##// END OF EJS Templates
Merge pull request #4920 from ellisonbg/pdf-formatter...
Brian E. Granger -
r15212:e68e13c3 merge
parent child Browse files
Show More
@@ -1,701 +1,772 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Top-level display functions for displaying object in different formats.
2 """Top-level display functions for displaying object in different formats.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import os
22 import os
23 import struct
23 import struct
24
24
25 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
25 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
26 unicode_type)
26 unicode_type)
27
27 from IPython.testing.skipdoctest import skip_doctest
28 from .displaypub import publish_display_data
28 from .displaypub import publish_display_data
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # utility functions
31 # utility functions
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 def _safe_exists(path):
34 def _safe_exists(path):
35 """Check path, but don't let exceptions raise"""
35 """Check path, but don't let exceptions raise"""
36 try:
36 try:
37 return os.path.exists(path)
37 return os.path.exists(path)
38 except Exception:
38 except Exception:
39 return False
39 return False
40
40
41 def _merge(d1, d2):
41 def _merge(d1, d2):
42 """Like update, but merges sub-dicts instead of clobbering at the top level.
42 """Like update, but merges sub-dicts instead of clobbering at the top level.
43
43
44 Updates d1 in-place
44 Updates d1 in-place
45 """
45 """
46
46
47 if not isinstance(d2, dict) or not isinstance(d1, dict):
47 if not isinstance(d2, dict) or not isinstance(d1, dict):
48 return d2
48 return d2
49 for key, value in d2.items():
49 for key, value in d2.items():
50 d1[key] = _merge(d1.get(key), value)
50 d1[key] = _merge(d1.get(key), value)
51 return d1
51 return d1
52
52
53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
54 """internal implementation of all display_foo methods
54 """internal implementation of all display_foo methods
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58 mimetype : str
58 mimetype : str
59 The mimetype to be published (e.g. 'image/png')
59 The mimetype to be published (e.g. 'image/png')
60 objs : tuple of objects
60 objs : tuple of objects
61 The Python objects to display, or if raw=True raw text data to
61 The Python objects to display, or if raw=True raw text data to
62 display.
62 display.
63 raw : bool
63 raw : bool
64 Are the data objects raw data or Python objects that need to be
64 Are the data objects raw data or Python objects that need to be
65 formatted before display? [default: False]
65 formatted before display? [default: False]
66 metadata : dict (optional)
66 metadata : dict (optional)
67 Metadata to be associated with the specific mimetype output.
67 Metadata to be associated with the specific mimetype output.
68 """
68 """
69 if metadata:
69 if metadata:
70 metadata = {mimetype: metadata}
70 metadata = {mimetype: metadata}
71 if raw:
71 if raw:
72 # turn list of pngdata into list of { 'image/png': pngdata }
72 # turn list of pngdata into list of { 'image/png': pngdata }
73 objs = [ {mimetype: obj} for obj in objs ]
73 objs = [ {mimetype: obj} for obj in objs ]
74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
75
75
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77 # Main functions
77 # Main functions
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79
79
80 def display(*objs, **kwargs):
80 def display(*objs, **kwargs):
81 """Display a Python object in all frontends.
81 """Display a Python object in all frontends.
82
82
83 By default all representations will be computed and sent to the frontends.
83 By default all representations will be computed and sent to the frontends.
84 Frontends can decide which representation is used and how.
84 Frontends can decide which representation is used and how.
85
85
86 Parameters
86 Parameters
87 ----------
87 ----------
88 objs : tuple of objects
88 objs : tuple of objects
89 The Python objects to display.
89 The Python objects to display.
90 raw : bool, optional
90 raw : bool, optional
91 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
91 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
92 or Python objects that need to be formatted before display? [default: False]
92 or Python objects that need to be formatted before display? [default: False]
93 include : list or tuple, optional
93 include : list or tuple, optional
94 A list of format type strings (MIME types) to include in the
94 A list of format type strings (MIME types) to include in the
95 format data dict. If this is set *only* the format types included
95 format data dict. If this is set *only* the format types included
96 in this list will be computed.
96 in this list will be computed.
97 exclude : list or tuple, optional
97 exclude : list or tuple, optional
98 A list of format type strings (MIME types) to exclude in the format
98 A list of format type strings (MIME types) to exclude in the format
99 data dict. If this is set all format types will be computed,
99 data dict. If this is set all format types will be computed,
100 except for those included in this argument.
100 except for those included in this argument.
101 metadata : dict, optional
101 metadata : dict, optional
102 A dictionary of metadata to associate with the output.
102 A dictionary of metadata to associate with the output.
103 mime-type keys in this dictionary will be associated with the individual
103 mime-type keys in this dictionary will be associated with the individual
104 representation formats, if they exist.
104 representation formats, if they exist.
105 """
105 """
106 raw = kwargs.get('raw', False)
106 raw = kwargs.get('raw', False)
107 include = kwargs.get('include')
107 include = kwargs.get('include')
108 exclude = kwargs.get('exclude')
108 exclude = kwargs.get('exclude')
109 metadata = kwargs.get('metadata')
109 metadata = kwargs.get('metadata')
110
110
111 from IPython.core.interactiveshell import InteractiveShell
111 from IPython.core.interactiveshell import InteractiveShell
112
112
113 if not raw:
113 if not raw:
114 format = InteractiveShell.instance().display_formatter.format
114 format = InteractiveShell.instance().display_formatter.format
115
115
116 for obj in objs:
116 for obj in objs:
117
117
118 # If _ipython_display_ is defined, use that to display this object.
118 # If _ipython_display_ is defined, use that to display this object.
119 display_method = getattr(obj, '_ipython_display_', None)
119 display_method = getattr(obj, '_ipython_display_', None)
120 if display_method is not None:
120 if display_method is not None:
121 try:
121 try:
122 display_method(**kwargs)
122 display_method(**kwargs)
123 except NotImplementedError:
123 except NotImplementedError:
124 pass
124 pass
125 else:
125 else:
126 continue
126 continue
127 if raw:
127 if raw:
128 publish_display_data('display', obj, metadata)
128 publish_display_data('display', obj, metadata)
129 else:
129 else:
130 format_dict, md_dict = format(obj, include=include, exclude=exclude)
130 format_dict, md_dict = format(obj, include=include, exclude=exclude)
131 if metadata:
131 if metadata:
132 # kwarg-specified metadata gets precedence
132 # kwarg-specified metadata gets precedence
133 _merge(md_dict, metadata)
133 _merge(md_dict, metadata)
134 publish_display_data('display', format_dict, md_dict)
134 publish_display_data('display', format_dict, md_dict)
135
135
136
136
137 def display_pretty(*objs, **kwargs):
137 def display_pretty(*objs, **kwargs):
138 """Display the pretty (default) representation of an object.
138 """Display the pretty (default) representation of an object.
139
139
140 Parameters
140 Parameters
141 ----------
141 ----------
142 objs : tuple of objects
142 objs : tuple of objects
143 The Python objects to display, or if raw=True raw text data to
143 The Python objects to display, or if raw=True raw text data to
144 display.
144 display.
145 raw : bool
145 raw : bool
146 Are the data objects raw data or Python objects that need to be
146 Are the data objects raw data or Python objects that need to be
147 formatted before display? [default: False]
147 formatted before display? [default: False]
148 metadata : dict (optional)
148 metadata : dict (optional)
149 Metadata to be associated with the specific mimetype output.
149 Metadata to be associated with the specific mimetype output.
150 """
150 """
151 _display_mimetype('text/plain', objs, **kwargs)
151 _display_mimetype('text/plain', objs, **kwargs)
152
152
153
153
154 def display_html(*objs, **kwargs):
154 def display_html(*objs, **kwargs):
155 """Display the HTML representation of an object.
155 """Display the HTML representation of an object.
156
156
157 Parameters
157 Parameters
158 ----------
158 ----------
159 objs : tuple of objects
159 objs : tuple of objects
160 The Python objects to display, or if raw=True raw HTML data to
160 The Python objects to display, or if raw=True raw HTML data to
161 display.
161 display.
162 raw : bool
162 raw : bool
163 Are the data objects raw data or Python objects that need to be
163 Are the data objects raw data or Python objects that need to be
164 formatted before display? [default: False]
164 formatted before display? [default: False]
165 metadata : dict (optional)
165 metadata : dict (optional)
166 Metadata to be associated with the specific mimetype output.
166 Metadata to be associated with the specific mimetype output.
167 """
167 """
168 _display_mimetype('text/html', objs, **kwargs)
168 _display_mimetype('text/html', objs, **kwargs)
169
169
170
170
171 def display_svg(*objs, **kwargs):
171 def display_svg(*objs, **kwargs):
172 """Display the SVG representation of an object.
172 """Display the SVG representation of an object.
173
173
174 Parameters
174 Parameters
175 ----------
175 ----------
176 objs : tuple of objects
176 objs : tuple of objects
177 The Python objects to display, or if raw=True raw svg data to
177 The Python objects to display, or if raw=True raw svg data to
178 display.
178 display.
179 raw : bool
179 raw : bool
180 Are the data objects raw data or Python objects that need to be
180 Are the data objects raw data or Python objects that need to be
181 formatted before display? [default: False]
181 formatted before display? [default: False]
182 metadata : dict (optional)
182 metadata : dict (optional)
183 Metadata to be associated with the specific mimetype output.
183 Metadata to be associated with the specific mimetype output.
184 """
184 """
185 _display_mimetype('image/svg+xml', objs, **kwargs)
185 _display_mimetype('image/svg+xml', objs, **kwargs)
186
186
187
187
188 def display_png(*objs, **kwargs):
188 def display_png(*objs, **kwargs):
189 """Display the PNG representation of an object.
189 """Display the PNG representation of an object.
190
190
191 Parameters
191 Parameters
192 ----------
192 ----------
193 objs : tuple of objects
193 objs : tuple of objects
194 The Python objects to display, or if raw=True raw png data to
194 The Python objects to display, or if raw=True raw png data to
195 display.
195 display.
196 raw : bool
196 raw : bool
197 Are the data objects raw data or Python objects that need to be
197 Are the data objects raw data or Python objects that need to be
198 formatted before display? [default: False]
198 formatted before display? [default: False]
199 metadata : dict (optional)
199 metadata : dict (optional)
200 Metadata to be associated with the specific mimetype output.
200 Metadata to be associated with the specific mimetype output.
201 """
201 """
202 _display_mimetype('image/png', objs, **kwargs)
202 _display_mimetype('image/png', objs, **kwargs)
203
203
204
204
205 def display_jpeg(*objs, **kwargs):
205 def display_jpeg(*objs, **kwargs):
206 """Display the JPEG representation of an object.
206 """Display the JPEG representation of an object.
207
207
208 Parameters
208 Parameters
209 ----------
209 ----------
210 objs : tuple of objects
210 objs : tuple of objects
211 The Python objects to display, or if raw=True raw JPEG data to
211 The Python objects to display, or if raw=True raw JPEG data to
212 display.
212 display.
213 raw : bool
213 raw : bool
214 Are the data objects raw data or Python objects that need to be
214 Are the data objects raw data or Python objects that need to be
215 formatted before display? [default: False]
215 formatted before display? [default: False]
216 metadata : dict (optional)
216 metadata : dict (optional)
217 Metadata to be associated with the specific mimetype output.
217 Metadata to be associated with the specific mimetype output.
218 """
218 """
219 _display_mimetype('image/jpeg', objs, **kwargs)
219 _display_mimetype('image/jpeg', objs, **kwargs)
220
220
221
221
222 def display_latex(*objs, **kwargs):
222 def display_latex(*objs, **kwargs):
223 """Display the LaTeX representation of an object.
223 """Display the LaTeX representation of an object.
224
224
225 Parameters
225 Parameters
226 ----------
226 ----------
227 objs : tuple of objects
227 objs : tuple of objects
228 The Python objects to display, or if raw=True raw latex data to
228 The Python objects to display, or if raw=True raw latex data to
229 display.
229 display.
230 raw : bool
230 raw : bool
231 Are the data objects raw data or Python objects that need to be
231 Are the data objects raw data or Python objects that need to be
232 formatted before display? [default: False]
232 formatted before display? [default: False]
233 metadata : dict (optional)
233 metadata : dict (optional)
234 Metadata to be associated with the specific mimetype output.
234 Metadata to be associated with the specific mimetype output.
235 """
235 """
236 _display_mimetype('text/latex', objs, **kwargs)
236 _display_mimetype('text/latex', objs, **kwargs)
237
237
238
238
239 def display_json(*objs, **kwargs):
239 def display_json(*objs, **kwargs):
240 """Display the JSON representation of an object.
240 """Display the JSON representation of an object.
241
241
242 Note that not many frontends support displaying JSON.
242 Note that not many frontends support displaying JSON.
243
243
244 Parameters
244 Parameters
245 ----------
245 ----------
246 objs : tuple of objects
246 objs : tuple of objects
247 The Python objects to display, or if raw=True raw json data to
247 The Python objects to display, or if raw=True raw json data to
248 display.
248 display.
249 raw : bool
249 raw : bool
250 Are the data objects raw data or Python objects that need to be
250 Are the data objects raw data or Python objects that need to be
251 formatted before display? [default: False]
251 formatted before display? [default: False]
252 metadata : dict (optional)
252 metadata : dict (optional)
253 Metadata to be associated with the specific mimetype output.
253 Metadata to be associated with the specific mimetype output.
254 """
254 """
255 _display_mimetype('application/json', objs, **kwargs)
255 _display_mimetype('application/json', objs, **kwargs)
256
256
257
257
258 def display_javascript(*objs, **kwargs):
258 def display_javascript(*objs, **kwargs):
259 """Display the Javascript representation of an object.
259 """Display the Javascript representation of an object.
260
260
261 Parameters
261 Parameters
262 ----------
262 ----------
263 objs : tuple of objects
263 objs : tuple of objects
264 The Python objects to display, or if raw=True raw javascript data to
264 The Python objects to display, or if raw=True raw javascript data to
265 display.
265 display.
266 raw : bool
266 raw : bool
267 Are the data objects raw data or Python objects that need to be
267 Are the data objects raw data or Python objects that need to be
268 formatted before display? [default: False]
268 formatted before display? [default: False]
269 metadata : dict (optional)
269 metadata : dict (optional)
270 Metadata to be associated with the specific mimetype output.
270 Metadata to be associated with the specific mimetype output.
271 """
271 """
272 _display_mimetype('application/javascript', objs, **kwargs)
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 # Smart classes
293 # Smart classes
276 #-----------------------------------------------------------------------------
294 #-----------------------------------------------------------------------------
277
295
278
296
279 class DisplayObject(object):
297 class DisplayObject(object):
280 """An object that wraps data to be displayed."""
298 """An object that wraps data to be displayed."""
281
299
282 _read_flags = 'r'
300 _read_flags = 'r'
283
301
284 def __init__(self, data=None, url=None, filename=None):
302 def __init__(self, data=None, url=None, filename=None):
285 """Create a display object given raw data.
303 """Create a display object given raw data.
286
304
287 When this object is returned by an expression or passed to the
305 When this object is returned by an expression or passed to the
288 display function, it will result in the data being displayed
306 display function, it will result in the data being displayed
289 in the frontend. The MIME type of the data should match the
307 in the frontend. The MIME type of the data should match the
290 subclasses used, so the Png subclass should be used for 'image/png'
308 subclasses used, so the Png subclass should be used for 'image/png'
291 data. If the data is a URL, the data will first be downloaded
309 data. If the data is a URL, the data will first be downloaded
292 and then displayed. If
310 and then displayed. If
293
311
294 Parameters
312 Parameters
295 ----------
313 ----------
296 data : unicode, str or bytes
314 data : unicode, str or bytes
297 The raw data or a URL or file to load the data from
315 The raw data or a URL or file to load the data from
298 url : unicode
316 url : unicode
299 A URL to download the data from.
317 A URL to download the data from.
300 filename : unicode
318 filename : unicode
301 Path to a local file to load the data from.
319 Path to a local file to load the data from.
302 """
320 """
303 if data is not None and isinstance(data, string_types):
321 if data is not None and isinstance(data, string_types):
304 if data.startswith('http') and url is None:
322 if data.startswith('http') and url is None:
305 url = data
323 url = data
306 filename = None
324 filename = None
307 data = None
325 data = None
308 elif _safe_exists(data) and filename is None:
326 elif _safe_exists(data) and filename is None:
309 url = None
327 url = None
310 filename = data
328 filename = data
311 data = None
329 data = None
312
330
313 self.data = data
331 self.data = data
314 self.url = url
332 self.url = url
315 self.filename = None if filename is None else unicode_type(filename)
333 self.filename = None if filename is None else unicode_type(filename)
316
334
317 self.reload()
335 self.reload()
318 self._check_data()
336 self._check_data()
319
337
320 def _check_data(self):
338 def _check_data(self):
321 """Override in subclasses if there's something to check."""
339 """Override in subclasses if there's something to check."""
322 pass
340 pass
323
341
324 def reload(self):
342 def reload(self):
325 """Reload the raw data from file or URL."""
343 """Reload the raw data from file or URL."""
326 if self.filename is not None:
344 if self.filename is not None:
327 with open(self.filename, self._read_flags) as f:
345 with open(self.filename, self._read_flags) as f:
328 self.data = f.read()
346 self.data = f.read()
329 elif self.url is not None:
347 elif self.url is not None:
330 try:
348 try:
331 try:
349 try:
332 from urllib.request import urlopen # Py3
350 from urllib.request import urlopen # Py3
333 except ImportError:
351 except ImportError:
334 from urllib2 import urlopen
352 from urllib2 import urlopen
335 response = urlopen(self.url)
353 response = urlopen(self.url)
336 self.data = response.read()
354 self.data = response.read()
337 # extract encoding from header, if there is one:
355 # extract encoding from header, if there is one:
338 encoding = None
356 encoding = None
339 for sub in response.headers['content-type'].split(';'):
357 for sub in response.headers['content-type'].split(';'):
340 sub = sub.strip()
358 sub = sub.strip()
341 if sub.startswith('charset'):
359 if sub.startswith('charset'):
342 encoding = sub.split('=')[-1].strip()
360 encoding = sub.split('=')[-1].strip()
343 break
361 break
344 # decode data, if an encoding was specified
362 # decode data, if an encoding was specified
345 if encoding:
363 if encoding:
346 self.data = self.data.decode(encoding, 'replace')
364 self.data = self.data.decode(encoding, 'replace')
347 except:
365 except:
348 self.data = None
366 self.data = None
349
367
350 class TextDisplayObject(DisplayObject):
368 class TextDisplayObject(DisplayObject):
351 """Validate that display data is text"""
369 """Validate that display data is text"""
352 def _check_data(self):
370 def _check_data(self):
353 if self.data is not None and not isinstance(self.data, string_types):
371 if self.data is not None and not isinstance(self.data, string_types):
354 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
372 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
355
373
356 class Pretty(TextDisplayObject):
374 class Pretty(TextDisplayObject):
357
375
358 def _repr_pretty_(self):
376 def _repr_pretty_(self):
359 return self.data
377 return self.data
360
378
361
379
362 class HTML(TextDisplayObject):
380 class HTML(TextDisplayObject):
363
381
364 def _repr_html_(self):
382 def _repr_html_(self):
365 return self.data
383 return self.data
366
384
367 def __html__(self):
385 def __html__(self):
368 """
386 """
369 This method exists to inform other HTML-using modules (e.g. Markupsafe,
387 This method exists to inform other HTML-using modules (e.g. Markupsafe,
370 htmltag, etc) that this object is HTML and does not need things like
388 htmltag, etc) that this object is HTML and does not need things like
371 special characters (<>&) escaped.
389 special characters (<>&) escaped.
372 """
390 """
373 return self._repr_html_()
391 return self._repr_html_()
374
392
375
393
376 class Math(TextDisplayObject):
394 class Math(TextDisplayObject):
377
395
378 def _repr_latex_(self):
396 def _repr_latex_(self):
379 s = self.data.strip('$')
397 s = self.data.strip('$')
380 return "$$%s$$" % s
398 return "$$%s$$" % s
381
399
382
400
383 class Latex(TextDisplayObject):
401 class Latex(TextDisplayObject):
384
402
385 def _repr_latex_(self):
403 def _repr_latex_(self):
386 return self.data
404 return self.data
387
405
388
406
389 class SVG(DisplayObject):
407 class SVG(DisplayObject):
390
408
391 # wrap data in a property, which extracts the <svg> tag, discarding
409 # wrap data in a property, which extracts the <svg> tag, discarding
392 # document headers
410 # document headers
393 _data = None
411 _data = None
394
412
395 @property
413 @property
396 def data(self):
414 def data(self):
397 return self._data
415 return self._data
398
416
399 @data.setter
417 @data.setter
400 def data(self, svg):
418 def data(self, svg):
401 if svg is None:
419 if svg is None:
402 self._data = None
420 self._data = None
403 return
421 return
404 # parse into dom object
422 # parse into dom object
405 from xml.dom import minidom
423 from xml.dom import minidom
406 svg = cast_bytes_py2(svg)
424 svg = cast_bytes_py2(svg)
407 x = minidom.parseString(svg)
425 x = minidom.parseString(svg)
408 # get svg tag (should be 1)
426 # get svg tag (should be 1)
409 found_svg = x.getElementsByTagName('svg')
427 found_svg = x.getElementsByTagName('svg')
410 if found_svg:
428 if found_svg:
411 svg = found_svg[0].toxml()
429 svg = found_svg[0].toxml()
412 else:
430 else:
413 # fallback on the input, trust the user
431 # fallback on the input, trust the user
414 # but this is probably an error.
432 # but this is probably an error.
415 pass
433 pass
416 svg = cast_unicode(svg)
434 svg = cast_unicode(svg)
417 self._data = svg
435 self._data = svg
418
436
419 def _repr_svg_(self):
437 def _repr_svg_(self):
420 return self.data
438 return self.data
421
439
422
440
423 class JSON(TextDisplayObject):
441 class JSON(TextDisplayObject):
424
442
425 def _repr_json_(self):
443 def _repr_json_(self):
426 return self.data
444 return self.data
427
445
428 css_t = """$("head").append($("<link/>").attr({
446 css_t = """$("head").append($("<link/>").attr({
429 rel: "stylesheet",
447 rel: "stylesheet",
430 type: "text/css",
448 type: "text/css",
431 href: "%s"
449 href: "%s"
432 }));
450 }));
433 """
451 """
434
452
435 lib_t1 = """$.getScript("%s", function () {
453 lib_t1 = """$.getScript("%s", function () {
436 """
454 """
437 lib_t2 = """});
455 lib_t2 = """});
438 """
456 """
439
457
440 class Javascript(TextDisplayObject):
458 class Javascript(TextDisplayObject):
441
459
442 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
460 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
443 """Create a Javascript display object given raw data.
461 """Create a Javascript display object given raw data.
444
462
445 When this object is returned by an expression or passed to the
463 When this object is returned by an expression or passed to the
446 display function, it will result in the data being displayed
464 display function, it will result in the data being displayed
447 in the frontend. If the data is a URL, the data will first be
465 in the frontend. If the data is a URL, the data will first be
448 downloaded and then displayed.
466 downloaded and then displayed.
449
467
450 In the Notebook, the containing element will be available as `element`,
468 In the Notebook, the containing element will be available as `element`,
451 and jQuery will be available. The output area starts hidden, so if
469 and jQuery will be available. The output area starts hidden, so if
452 the js appends content to `element` that should be visible, then
470 the js appends content to `element` that should be visible, then
453 it must call `container.show()` to unhide the area.
471 it must call `container.show()` to unhide the area.
454
472
455 Parameters
473 Parameters
456 ----------
474 ----------
457 data : unicode, str or bytes
475 data : unicode, str or bytes
458 The Javascript source code or a URL to download it from.
476 The Javascript source code or a URL to download it from.
459 url : unicode
477 url : unicode
460 A URL to download the data from.
478 A URL to download the data from.
461 filename : unicode
479 filename : unicode
462 Path to a local file to load the data from.
480 Path to a local file to load the data from.
463 lib : list or str
481 lib : list or str
464 A sequence of Javascript library URLs to load asynchronously before
482 A sequence of Javascript library URLs to load asynchronously before
465 running the source code. The full URLs of the libraries should
483 running the source code. The full URLs of the libraries should
466 be given. A single Javascript library URL can also be given as a
484 be given. A single Javascript library URL can also be given as a
467 string.
485 string.
468 css: : list or str
486 css: : list or str
469 A sequence of css files to load before running the source code.
487 A sequence of css files to load before running the source code.
470 The full URLs of the css files should be given. A single css URL
488 The full URLs of the css files should be given. A single css URL
471 can also be given as a string.
489 can also be given as a string.
472 """
490 """
473 if isinstance(lib, string_types):
491 if isinstance(lib, string_types):
474 lib = [lib]
492 lib = [lib]
475 elif lib is None:
493 elif lib is None:
476 lib = []
494 lib = []
477 if isinstance(css, string_types):
495 if isinstance(css, string_types):
478 css = [css]
496 css = [css]
479 elif css is None:
497 elif css is None:
480 css = []
498 css = []
481 if not isinstance(lib, (list,tuple)):
499 if not isinstance(lib, (list,tuple)):
482 raise TypeError('expected sequence, got: %r' % lib)
500 raise TypeError('expected sequence, got: %r' % lib)
483 if not isinstance(css, (list,tuple)):
501 if not isinstance(css, (list,tuple)):
484 raise TypeError('expected sequence, got: %r' % css)
502 raise TypeError('expected sequence, got: %r' % css)
485 self.lib = lib
503 self.lib = lib
486 self.css = css
504 self.css = css
487 super(Javascript, self).__init__(data=data, url=url, filename=filename)
505 super(Javascript, self).__init__(data=data, url=url, filename=filename)
488
506
489 def _repr_javascript_(self):
507 def _repr_javascript_(self):
490 r = ''
508 r = ''
491 for c in self.css:
509 for c in self.css:
492 r += css_t % c
510 r += css_t % c
493 for l in self.lib:
511 for l in self.lib:
494 r += lib_t1 % l
512 r += lib_t1 % l
495 r += self.data
513 r += self.data
496 r += lib_t2*len(self.lib)
514 r += lib_t2*len(self.lib)
497 return r
515 return r
498
516
499 # constants for identifying png/jpeg data
517 # constants for identifying png/jpeg data
500 _PNG = b'\x89PNG\r\n\x1a\n'
518 _PNG = b'\x89PNG\r\n\x1a\n'
501 _JPEG = b'\xff\xd8'
519 _JPEG = b'\xff\xd8'
502
520
503 def _pngxy(data):
521 def _pngxy(data):
504 """read the (width, height) from a PNG header"""
522 """read the (width, height) from a PNG header"""
505 ihdr = data.index(b'IHDR')
523 ihdr = data.index(b'IHDR')
506 # next 8 bytes are width/height
524 # next 8 bytes are width/height
507 w4h4 = data[ihdr+4:ihdr+12]
525 w4h4 = data[ihdr+4:ihdr+12]
508 return struct.unpack('>ii', w4h4)
526 return struct.unpack('>ii', w4h4)
509
527
510 def _jpegxy(data):
528 def _jpegxy(data):
511 """read the (width, height) from a JPEG header"""
529 """read the (width, height) from a JPEG header"""
512 # adapted from http://www.64lines.com/jpeg-width-height
530 # adapted from http://www.64lines.com/jpeg-width-height
513
531
514 idx = 4
532 idx = 4
515 while True:
533 while True:
516 block_size = struct.unpack('>H', data[idx:idx+2])[0]
534 block_size = struct.unpack('>H', data[idx:idx+2])[0]
517 idx = idx + block_size
535 idx = idx + block_size
518 if data[idx:idx+2] == b'\xFF\xC0':
536 if data[idx:idx+2] == b'\xFF\xC0':
519 # found Start of Frame
537 # found Start of Frame
520 iSOF = idx
538 iSOF = idx
521 break
539 break
522 else:
540 else:
523 # read another block
541 # read another block
524 idx += 2
542 idx += 2
525
543
526 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
544 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
527 return w, h
545 return w, h
528
546
529 class Image(DisplayObject):
547 class Image(DisplayObject):
530
548
531 _read_flags = 'rb'
549 _read_flags = 'rb'
532 _FMT_JPEG = u'jpeg'
550 _FMT_JPEG = u'jpeg'
533 _FMT_PNG = u'png'
551 _FMT_PNG = u'png'
534 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
552 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
535
553
536 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
554 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
537 """Create a PNG/JPEG image object given raw data.
555 """Create a PNG/JPEG image object given raw data.
538
556
539 When this object is returned by an input cell or passed to the
557 When this object is returned by an input cell or passed to the
540 display function, it will result in the image being displayed
558 display function, it will result in the image being displayed
541 in the frontend.
559 in the frontend.
542
560
543 Parameters
561 Parameters
544 ----------
562 ----------
545 data : unicode, str or bytes
563 data : unicode, str or bytes
546 The raw image data or a URL or filename to load the data from.
564 The raw image data or a URL or filename to load the data from.
547 This always results in embedded image data.
565 This always results in embedded image data.
548 url : unicode
566 url : unicode
549 A URL to download the data from. If you specify `url=`,
567 A URL to download the data from. If you specify `url=`,
550 the image data will not be embedded unless you also specify `embed=True`.
568 the image data will not be embedded unless you also specify `embed=True`.
551 filename : unicode
569 filename : unicode
552 Path to a local file to load the data from.
570 Path to a local file to load the data from.
553 Images from a file are always embedded.
571 Images from a file are always embedded.
554 format : unicode
572 format : unicode
555 The format of the image data (png/jpeg/jpg). If a filename or URL is given
573 The format of the image data (png/jpeg/jpg). If a filename or URL is given
556 for format will be inferred from the filename extension.
574 for format will be inferred from the filename extension.
557 embed : bool
575 embed : bool
558 Should the image data be embedded using a data URI (True) or be
576 Should the image data be embedded using a data URI (True) or be
559 loaded using an <img> tag. Set this to True if you want the image
577 loaded using an <img> tag. Set this to True if you want the image
560 to be viewable later with no internet connection in the notebook.
578 to be viewable later with no internet connection in the notebook.
561
579
562 Default is `True`, unless the keyword argument `url` is set, then
580 Default is `True`, unless the keyword argument `url` is set, then
563 default value is `False`.
581 default value is `False`.
564
582
565 Note that QtConsole is not able to display images if `embed` is set to `False`
583 Note that QtConsole is not able to display images if `embed` is set to `False`
566 width : int
584 width : int
567 Width to which to constrain the image in html
585 Width to which to constrain the image in html
568 height : int
586 height : int
569 Height to which to constrain the image in html
587 Height to which to constrain the image in html
570 retina : bool
588 retina : bool
571 Automatically set the width and height to half of the measured
589 Automatically set the width and height to half of the measured
572 width and height.
590 width and height.
573 This only works for embedded images because it reads the width/height
591 This only works for embedded images because it reads the width/height
574 from image data.
592 from image data.
575 For non-embedded images, you can just set the desired display width
593 For non-embedded images, you can just set the desired display width
576 and height directly.
594 and height directly.
577
595
578 Examples
596 Examples
579 --------
597 --------
580 # embedded image data, works in qtconsole and notebook
598 # embedded image data, works in qtconsole and notebook
581 # when passed positionally, the first arg can be any of raw image data,
599 # when passed positionally, the first arg can be any of raw image data,
582 # a URL, or a filename from which to load image data.
600 # a URL, or a filename from which to load image data.
583 # The result is always embedding image data for inline images.
601 # The result is always embedding image data for inline images.
584 Image('http://www.google.fr/images/srpr/logo3w.png')
602 Image('http://www.google.fr/images/srpr/logo3w.png')
585 Image('/path/to/image.jpg')
603 Image('/path/to/image.jpg')
586 Image(b'RAW_PNG_DATA...')
604 Image(b'RAW_PNG_DATA...')
587
605
588 # Specifying Image(url=...) does not embed the image data,
606 # Specifying Image(url=...) does not embed the image data,
589 # it only generates `<img>` tag with a link to the source.
607 # it only generates `<img>` tag with a link to the source.
590 # This will not work in the qtconsole or offline.
608 # This will not work in the qtconsole or offline.
591 Image(url='http://www.google.fr/images/srpr/logo3w.png')
609 Image(url='http://www.google.fr/images/srpr/logo3w.png')
592
610
593 """
611 """
594 if filename is not None:
612 if filename is not None:
595 ext = self._find_ext(filename)
613 ext = self._find_ext(filename)
596 elif url is not None:
614 elif url is not None:
597 ext = self._find_ext(url)
615 ext = self._find_ext(url)
598 elif data is None:
616 elif data is None:
599 raise ValueError("No image data found. Expecting filename, url, or data.")
617 raise ValueError("No image data found. Expecting filename, url, or data.")
600 elif isinstance(data, string_types) and (
618 elif isinstance(data, string_types) and (
601 data.startswith('http') or _safe_exists(data)
619 data.startswith('http') or _safe_exists(data)
602 ):
620 ):
603 ext = self._find_ext(data)
621 ext = self._find_ext(data)
604 else:
622 else:
605 ext = None
623 ext = None
606
624
607 if ext is not None:
625 if ext is not None:
608 format = ext.lower()
626 format = ext.lower()
609 if ext == u'jpg' or ext == u'jpeg':
627 if ext == u'jpg' or ext == u'jpeg':
610 format = self._FMT_JPEG
628 format = self._FMT_JPEG
611 if ext == u'png':
629 if ext == u'png':
612 format = self._FMT_PNG
630 format = self._FMT_PNG
613 elif isinstance(data, bytes) and format == 'png':
631 elif isinstance(data, bytes) and format == 'png':
614 # infer image type from image data header,
632 # infer image type from image data header,
615 # only if format might not have been specified.
633 # only if format might not have been specified.
616 if data[:2] == _JPEG:
634 if data[:2] == _JPEG:
617 format = 'jpeg'
635 format = 'jpeg'
618
636
619 self.format = unicode_type(format).lower()
637 self.format = unicode_type(format).lower()
620 self.embed = embed if embed is not None else (url is None)
638 self.embed = embed if embed is not None else (url is None)
621
639
622 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
640 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
623 raise ValueError("Cannot embed the '%s' image format" % (self.format))
641 raise ValueError("Cannot embed the '%s' image format" % (self.format))
624 self.width = width
642 self.width = width
625 self.height = height
643 self.height = height
626 self.retina = retina
644 self.retina = retina
627 super(Image, self).__init__(data=data, url=url, filename=filename)
645 super(Image, self).__init__(data=data, url=url, filename=filename)
628
646
629 if retina:
647 if retina:
630 self._retina_shape()
648 self._retina_shape()
631
649
632 def _retina_shape(self):
650 def _retina_shape(self):
633 """load pixel-doubled width and height from image data"""
651 """load pixel-doubled width and height from image data"""
634 if not self.embed:
652 if not self.embed:
635 return
653 return
636 if self.format == 'png':
654 if self.format == 'png':
637 w, h = _pngxy(self.data)
655 w, h = _pngxy(self.data)
638 elif self.format == 'jpeg':
656 elif self.format == 'jpeg':
639 w, h = _jpegxy(self.data)
657 w, h = _jpegxy(self.data)
640 else:
658 else:
641 # retina only supports png
659 # retina only supports png
642 return
660 return
643 self.width = w // 2
661 self.width = w // 2
644 self.height = h // 2
662 self.height = h // 2
645
663
646 def reload(self):
664 def reload(self):
647 """Reload the raw data from file or URL."""
665 """Reload the raw data from file or URL."""
648 if self.embed:
666 if self.embed:
649 super(Image,self).reload()
667 super(Image,self).reload()
650 if self.retina:
668 if self.retina:
651 self._retina_shape()
669 self._retina_shape()
652
670
653 def _repr_html_(self):
671 def _repr_html_(self):
654 if not self.embed:
672 if not self.embed:
655 width = height = ''
673 width = height = ''
656 if self.width:
674 if self.width:
657 width = ' width="%d"' % self.width
675 width = ' width="%d"' % self.width
658 if self.height:
676 if self.height:
659 height = ' height="%d"' % self.height
677 height = ' height="%d"' % self.height
660 return u'<img src="%s"%s%s/>' % (self.url, width, height)
678 return u'<img src="%s"%s%s/>' % (self.url, width, height)
661
679
662 def _data_and_metadata(self):
680 def _data_and_metadata(self):
663 """shortcut for returning metadata with shape information, if defined"""
681 """shortcut for returning metadata with shape information, if defined"""
664 md = {}
682 md = {}
665 if self.width:
683 if self.width:
666 md['width'] = self.width
684 md['width'] = self.width
667 if self.height:
685 if self.height:
668 md['height'] = self.height
686 md['height'] = self.height
669 if md:
687 if md:
670 return self.data, md
688 return self.data, md
671 else:
689 else:
672 return self.data
690 return self.data
673
691
674 def _repr_png_(self):
692 def _repr_png_(self):
675 if self.embed and self.format == u'png':
693 if self.embed and self.format == u'png':
676 return self._data_and_metadata()
694 return self._data_and_metadata()
677
695
678 def _repr_jpeg_(self):
696 def _repr_jpeg_(self):
679 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
697 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
680 return self._data_and_metadata()
698 return self._data_and_metadata()
681
699
682 def _find_ext(self, s):
700 def _find_ext(self, s):
683 return unicode_type(s.split('.')[-1].lower())
701 return unicode_type(s.split('.')[-1].lower())
684
702
685
703
686 def clear_output(wait=False):
704 def clear_output(wait=False):
687 """Clear the output of the current cell receiving output.
705 """Clear the output of the current cell receiving output.
688
706
689 Parameters
707 Parameters
690 ----------
708 ----------
691 wait : bool [default: false]
709 wait : bool [default: false]
692 Wait to clear the output until new output is available to replace it."""
710 Wait to clear the output until new output is available to replace it."""
693 from IPython.core.interactiveshell import InteractiveShell
711 from IPython.core.interactiveshell import InteractiveShell
694 if InteractiveShell.initialized():
712 if InteractiveShell.initialized():
695 InteractiveShell.instance().display_pub.clear_output(wait)
713 InteractiveShell.instance().display_pub.clear_output(wait)
696 else:
714 else:
697 from IPython.utils import io
715 from IPython.utils import io
698 print('\033[2K\r', file=io.stdout, end='')
716 print('\033[2K\r', file=io.stdout, end='')
699 io.stdout.flush()
717 io.stdout.flush()
700 print('\033[2K\r', file=io.stderr, end='')
718 print('\033[2K\r', file=io.stderr, end='')
701 io.stderr.flush()
719 io.stderr.flush()
720
721
722 @skip_doctest
723 def set_matplotlib_formats(*formats, **kwargs):
724 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
725
726 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
727
728 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
729
730 To set this in your config files use the following::
731
732 c.InlineBackend.figure_formats = {'pdf', 'png', 'svg'}
733 c.InlineBackend.quality = 90
734
735 Parameters
736 ----------
737 *formats : list, tuple
738 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
739 quality : int
740 A percentage for the quality of JPEG figures. Defaults to 90.
741 """
742 from IPython.core.interactiveshell import InteractiveShell
743 from IPython.core.pylabtools import select_figure_formats
744 shell = InteractiveShell.instance()
745 select_figure_formats(shell, formats, quality=90)
746
747 @skip_doctest
748 def set_matplotlib_close(close):
749 """Set whether the inline backend closes all figures automatically or not.
750
751 By default, the inline backend used in the IPython Notebook will close all
752 matplotlib figures automatically after each cell is run. This means that
753 plots in different cells won't interfere. Sometimes, you may want to make
754 a plot in one cell and then refine it in later cells. This can be accomplished
755 by::
756
757 In [1]: set_matplotlib_close(False)
758
759 To set this in your config files use the following::
760
761 c.InlineBackend.close_figures = False
762
763 Parameters
764 ----------
765 close : bool
766 Should all matplotlib figures be automatically closed after each cell is
767 run?
768 """
769 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
770 ilbe = InlineBackend.instance()
771 ilbe.close_figures = close
772
@@ -1,825 +1,846 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Display formatters.
2 """Display formatters.
3
3
4 Inheritance diagram:
4 Inheritance diagram:
5
5
6 .. inheritance-diagram:: IPython.core.formatters
6 .. inheritance-diagram:: IPython.core.formatters
7 :parts: 3
7 :parts: 3
8
8
9 Authors:
9 Authors:
10
10
11 * Robert Kern
11 * Robert Kern
12 * Brian Granger
12 * Brian Granger
13 """
13 """
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2010-2011, IPython Development Team.
15 # Copyright (C) 2010-2011, IPython Development Team.
16 #
16 #
17 # Distributed under the terms of the Modified BSD License.
17 # Distributed under the terms of the Modified BSD License.
18 #
18 #
19 # The full license is in the file COPYING.txt, distributed with this software.
19 # The full license is in the file COPYING.txt, distributed with this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 # Stdlib imports
26 # Stdlib imports
27 import abc
27 import abc
28 import sys
28 import sys
29 import warnings
29 import warnings
30
30
31 from IPython.external.decorator import decorator
31 from IPython.external.decorator import decorator
32
32
33 # Our own imports
33 # Our own imports
34 from IPython.config.configurable import Configurable
34 from IPython.config.configurable import Configurable
35 from IPython.lib import pretty
35 from IPython.lib import pretty
36 from IPython.utils import io
36 from IPython.utils import io
37 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
38 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
38 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
39 )
39 )
40 from IPython.utils.warn import warn
40 from IPython.utils.warn import warn
41 from IPython.utils.py3compat import (
41 from IPython.utils.py3compat import (
42 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
42 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
43 )
43 )
44
44
45 if PY3:
45 if PY3:
46 from io import StringIO
46 from io import StringIO
47 else:
47 else:
48 from StringIO import StringIO
48 from StringIO import StringIO
49
49
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # The main DisplayFormatter class
52 # The main DisplayFormatter class
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55 class DisplayFormatter(Configurable):
55 class DisplayFormatter(Configurable):
56
56
57 # When set to true only the default plain text formatter will be used.
57 # When set to true only the default plain text formatter will be used.
58 plain_text_only = Bool(False, config=True)
58 plain_text_only = Bool(False, config=True)
59 def _plain_text_only_changed(self, name, old, new):
59 def _plain_text_only_changed(self, name, old, new):
60 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
60 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
61
61
62 Use DisplayFormatter.active_types = ['text/plain']
62 Use DisplayFormatter.active_types = ['text/plain']
63 for the same effect.
63 for the same effect.
64 """, DeprecationWarning)
64 """, DeprecationWarning)
65 if new:
65 if new:
66 self.active_types = ['text/plain']
66 self.active_types = ['text/plain']
67 else:
67 else:
68 self.active_types = self.format_types
68 self.active_types = self.format_types
69
69
70 active_types = List(Unicode, config=True,
70 active_types = List(Unicode, config=True,
71 help="""List of currently active mime-types to display.
71 help="""List of currently active mime-types to display.
72 You can use this to set a white-list for formats to display.
72 You can use this to set a white-list for formats to display.
73
73
74 Most users will not need to change this value.
74 Most users will not need to change this value.
75 """)
75 """)
76 def _active_types_default(self):
76 def _active_types_default(self):
77 return self.format_types
77 return self.format_types
78
78
79 def _active_types_changed(self, name, old, new):
79 def _active_types_changed(self, name, old, new):
80 for key, formatter in self.formatters.items():
80 for key, formatter in self.formatters.items():
81 if key in new:
81 if key in new:
82 formatter.enabled = True
82 formatter.enabled = True
83 else:
83 else:
84 formatter.enabled = False
84 formatter.enabled = False
85
85
86 # A dict of formatter whose keys are format types (MIME types) and whose
86 # A dict of formatter whose keys are format types (MIME types) and whose
87 # values are subclasses of BaseFormatter.
87 # values are subclasses of BaseFormatter.
88 formatters = Dict()
88 formatters = Dict()
89 def _formatters_default(self):
89 def _formatters_default(self):
90 """Activate the default formatters."""
90 """Activate the default formatters."""
91 formatter_classes = [
91 formatter_classes = [
92 PlainTextFormatter,
92 PlainTextFormatter,
93 HTMLFormatter,
93 HTMLFormatter,
94 SVGFormatter,
94 SVGFormatter,
95 PNGFormatter,
95 PNGFormatter,
96 PDFFormatter,
96 JPEGFormatter,
97 JPEGFormatter,
97 LatexFormatter,
98 LatexFormatter,
98 JSONFormatter,
99 JSONFormatter,
99 JavascriptFormatter
100 JavascriptFormatter
100 ]
101 ]
101 d = {}
102 d = {}
102 for cls in formatter_classes:
103 for cls in formatter_classes:
103 f = cls(parent=self)
104 f = cls(parent=self)
104 d[f.format_type] = f
105 d[f.format_type] = f
105 return d
106 return d
106
107
107 def format(self, obj, include=None, exclude=None):
108 def format(self, obj, include=None, exclude=None):
108 """Return a format data dict for an object.
109 """Return a format data dict for an object.
109
110
110 By default all format types will be computed.
111 By default all format types will be computed.
111
112
112 The following MIME types are currently implemented:
113 The following MIME types are currently implemented:
113
114
114 * text/plain
115 * text/plain
115 * text/html
116 * text/html
116 * text/latex
117 * text/latex
117 * application/json
118 * application/json
118 * application/javascript
119 * application/javascript
120 * application/pdf
119 * image/png
121 * image/png
120 * image/jpeg
122 * image/jpeg
121 * image/svg+xml
123 * image/svg+xml
122
124
123 Parameters
125 Parameters
124 ----------
126 ----------
125 obj : object
127 obj : object
126 The Python object whose format data will be computed.
128 The Python object whose format data will be computed.
127 include : list or tuple, optional
129 include : list or tuple, optional
128 A list of format type strings (MIME types) to include in the
130 A list of format type strings (MIME types) to include in the
129 format data dict. If this is set *only* the format types included
131 format data dict. If this is set *only* the format types included
130 in this list will be computed.
132 in this list will be computed.
131 exclude : list or tuple, optional
133 exclude : list or tuple, optional
132 A list of format type string (MIME types) to exclude in the format
134 A list of format type string (MIME types) to exclude in the format
133 data dict. If this is set all format types will be computed,
135 data dict. If this is set all format types will be computed,
134 except for those included in this argument.
136 except for those included in this argument.
135
137
136 Returns
138 Returns
137 -------
139 -------
138 (format_dict, metadata_dict) : tuple of two dicts
140 (format_dict, metadata_dict) : tuple of two dicts
139
141
140 format_dict is a dictionary of key/value pairs, one of each format that was
142 format_dict is a dictionary of key/value pairs, one of each format that was
141 generated for the object. The keys are the format types, which
143 generated for the object. The keys are the format types, which
142 will usually be MIME type strings and the values and JSON'able
144 will usually be MIME type strings and the values and JSON'able
143 data structure containing the raw data for the representation in
145 data structure containing the raw data for the representation in
144 that format.
146 that format.
145
147
146 metadata_dict is a dictionary of metadata about each mime-type output.
148 metadata_dict is a dictionary of metadata about each mime-type output.
147 Its keys will be a strict subset of the keys in format_dict.
149 Its keys will be a strict subset of the keys in format_dict.
148 """
150 """
149 format_dict = {}
151 format_dict = {}
150 md_dict = {}
152 md_dict = {}
151
153
152 for format_type, formatter in self.formatters.items():
154 for format_type, formatter in self.formatters.items():
153 if include and format_type not in include:
155 if include and format_type not in include:
154 continue
156 continue
155 if exclude and format_type in exclude:
157 if exclude and format_type in exclude:
156 continue
158 continue
157
159
158 md = None
160 md = None
159 try:
161 try:
160 data = formatter(obj)
162 data = formatter(obj)
161 except:
163 except:
162 # FIXME: log the exception
164 # FIXME: log the exception
163 raise
165 raise
164
166
165 # formatters can return raw data or (data, metadata)
167 # formatters can return raw data or (data, metadata)
166 if isinstance(data, tuple) and len(data) == 2:
168 if isinstance(data, tuple) and len(data) == 2:
167 data, md = data
169 data, md = data
168
170
169 if data is not None:
171 if data is not None:
170 format_dict[format_type] = data
172 format_dict[format_type] = data
171 if md is not None:
173 if md is not None:
172 md_dict[format_type] = md
174 md_dict[format_type] = md
173
175
174 return format_dict, md_dict
176 return format_dict, md_dict
175
177
176 @property
178 @property
177 def format_types(self):
179 def format_types(self):
178 """Return the format types (MIME types) of the active formatters."""
180 """Return the format types (MIME types) of the active formatters."""
179 return list(self.formatters.keys())
181 return list(self.formatters.keys())
180
182
181
183
182 #-----------------------------------------------------------------------------
184 #-----------------------------------------------------------------------------
183 # Formatters for specific format types (text, html, svg, etc.)
185 # Formatters for specific format types (text, html, svg, etc.)
184 #-----------------------------------------------------------------------------
186 #-----------------------------------------------------------------------------
185
187
186 class FormatterWarning(UserWarning):
188 class FormatterWarning(UserWarning):
187 """Warning class for errors in formatters"""
189 """Warning class for errors in formatters"""
188
190
189 @decorator
191 @decorator
190 def warn_format_error(method, self, *args, **kwargs):
192 def warn_format_error(method, self, *args, **kwargs):
191 """decorator for warning on failed format call"""
193 """decorator for warning on failed format call"""
192 try:
194 try:
193 r = method(self, *args, **kwargs)
195 r = method(self, *args, **kwargs)
194 except NotImplementedError as e:
196 except NotImplementedError as e:
195 # don't warn on NotImplementedErrors
197 # don't warn on NotImplementedErrors
196 return None
198 return None
197 except Exception as e:
199 except Exception as e:
198 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
200 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
199 FormatterWarning,
201 FormatterWarning,
200 )
202 )
201 return None
203 return None
202 if r is None or isinstance(r, self._return_type) or \
204 if r is None or isinstance(r, self._return_type) or \
203 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
205 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
204 return r
206 return r
205 else:
207 else:
206 warnings.warn(
208 warnings.warn(
207 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
209 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
208 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
210 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
209 FormatterWarning
211 FormatterWarning
210 )
212 )
211
213
212
214
213 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
215 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
214 """ Abstract base class for Formatters.
216 """ Abstract base class for Formatters.
215
217
216 A formatter is a callable class that is responsible for computing the
218 A formatter is a callable class that is responsible for computing the
217 raw format data for a particular format type (MIME type). For example,
219 raw format data for a particular format type (MIME type). For example,
218 an HTML formatter would have a format type of `text/html` and would return
220 an HTML formatter would have a format type of `text/html` and would return
219 the HTML representation of the object when called.
221 the HTML representation of the object when called.
220 """
222 """
221
223
222 # The format type of the data returned, usually a MIME type.
224 # The format type of the data returned, usually a MIME type.
223 format_type = 'text/plain'
225 format_type = 'text/plain'
224
226
225 # Is the formatter enabled...
227 # Is the formatter enabled...
226 enabled = True
228 enabled = True
227
229
228 @abc.abstractmethod
230 @abc.abstractmethod
229 @warn_format_error
231 @warn_format_error
230 def __call__(self, obj):
232 def __call__(self, obj):
231 """Return a JSON'able representation of the object.
233 """Return a JSON'able representation of the object.
232
234
233 If the object cannot be formatted by this formatter,
235 If the object cannot be formatted by this formatter,
234 warn and return None.
236 warn and return None.
235 """
237 """
236 return repr(obj)
238 return repr(obj)
237
239
238
240
239 def _mod_name_key(typ):
241 def _mod_name_key(typ):
240 """Return a (__module__, __name__) tuple for a type.
242 """Return a (__module__, __name__) tuple for a type.
241
243
242 Used as key in Formatter.deferred_printers.
244 Used as key in Formatter.deferred_printers.
243 """
245 """
244 module = getattr(typ, '__module__', None)
246 module = getattr(typ, '__module__', None)
245 name = getattr(typ, '__name__', None)
247 name = getattr(typ, '__name__', None)
246 return (module, name)
248 return (module, name)
247
249
248
250
249 def _get_type(obj):
251 def _get_type(obj):
250 """Return the type of an instance (old and new-style)"""
252 """Return the type of an instance (old and new-style)"""
251 return getattr(obj, '__class__', None) or type(obj)
253 return getattr(obj, '__class__', None) or type(obj)
252
254
253 _raise_key_error = object()
255 _raise_key_error = object()
254
256
255
257
256 class BaseFormatter(Configurable):
258 class BaseFormatter(Configurable):
257 """A base formatter class that is configurable.
259 """A base formatter class that is configurable.
258
260
259 This formatter should usually be used as the base class of all formatters.
261 This formatter should usually be used as the base class of all formatters.
260 It is a traited :class:`Configurable` class and includes an extensible
262 It is a traited :class:`Configurable` class and includes an extensible
261 API for users to determine how their objects are formatted. The following
263 API for users to determine how their objects are formatted. The following
262 logic is used to find a function to format an given object.
264 logic is used to find a function to format an given object.
263
265
264 1. The object is introspected to see if it has a method with the name
266 1. The object is introspected to see if it has a method with the name
265 :attr:`print_method`. If is does, that object is passed to that method
267 :attr:`print_method`. If is does, that object is passed to that method
266 for formatting.
268 for formatting.
267 2. If no print method is found, three internal dictionaries are consulted
269 2. If no print method is found, three internal dictionaries are consulted
268 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
270 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
269 and :attr:`deferred_printers`.
271 and :attr:`deferred_printers`.
270
272
271 Users should use these dictionaries to register functions that will be
273 Users should use these dictionaries to register functions that will be
272 used to compute the format data for their objects (if those objects don't
274 used to compute the format data for their objects (if those objects don't
273 have the special print methods). The easiest way of using these
275 have the special print methods). The easiest way of using these
274 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
276 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
275 methods.
277 methods.
276
278
277 If no function/callable is found to compute the format data, ``None`` is
279 If no function/callable is found to compute the format data, ``None`` is
278 returned and this format type is not used.
280 returned and this format type is not used.
279 """
281 """
280
282
281 format_type = Unicode('text/plain')
283 format_type = Unicode('text/plain')
282 _return_type = string_types
284 _return_type = string_types
283
285
284 enabled = Bool(True, config=True)
286 enabled = Bool(True, config=True)
285
287
286 print_method = ObjectName('__repr__')
288 print_method = ObjectName('__repr__')
287
289
288 # The singleton printers.
290 # The singleton printers.
289 # Maps the IDs of the builtin singleton objects to the format functions.
291 # Maps the IDs of the builtin singleton objects to the format functions.
290 singleton_printers = Dict(config=True)
292 singleton_printers = Dict(config=True)
291
293
292 # The type-specific printers.
294 # The type-specific printers.
293 # Map type objects to the format functions.
295 # Map type objects to the format functions.
294 type_printers = Dict(config=True)
296 type_printers = Dict(config=True)
295
297
296 # The deferred-import type-specific printers.
298 # The deferred-import type-specific printers.
297 # Map (modulename, classname) pairs to the format functions.
299 # Map (modulename, classname) pairs to the format functions.
298 deferred_printers = Dict(config=True)
300 deferred_printers = Dict(config=True)
299
301
300 @warn_format_error
302 @warn_format_error
301 def __call__(self, obj):
303 def __call__(self, obj):
302 """Compute the format for an object."""
304 """Compute the format for an object."""
303 if self.enabled:
305 if self.enabled:
304 # lookup registered printer
306 # lookup registered printer
305 try:
307 try:
306 printer = self.lookup(obj)
308 printer = self.lookup(obj)
307 except KeyError:
309 except KeyError:
308 pass
310 pass
309 else:
311 else:
310 return printer(obj)
312 return printer(obj)
311 # Finally look for special method names
313 # Finally look for special method names
312 method = pretty._safe_getattr(obj, self.print_method, None)
314 method = pretty._safe_getattr(obj, self.print_method, None)
313 if method is not None:
315 if method is not None:
314 return method()
316 return method()
315 return None
317 return None
316 else:
318 else:
317 return None
319 return None
318
320
319 def __contains__(self, typ):
321 def __contains__(self, typ):
320 """map in to lookup_by_type"""
322 """map in to lookup_by_type"""
321 try:
323 try:
322 self.lookup_by_type(typ)
324 self.lookup_by_type(typ)
323 except KeyError:
325 except KeyError:
324 return False
326 return False
325 else:
327 else:
326 return True
328 return True
327
329
328 def lookup(self, obj):
330 def lookup(self, obj):
329 """Look up the formatter for a given instance.
331 """Look up the formatter for a given instance.
330
332
331 Parameters
333 Parameters
332 ----------
334 ----------
333 obj : object instance
335 obj : object instance
334
336
335 Returns
337 Returns
336 -------
338 -------
337 f : callable
339 f : callable
338 The registered formatting callable for the type.
340 The registered formatting callable for the type.
339
341
340 Raises
342 Raises
341 ------
343 ------
342 KeyError if the type has not been registered.
344 KeyError if the type has not been registered.
343 """
345 """
344 # look for singleton first
346 # look for singleton first
345 obj_id = id(obj)
347 obj_id = id(obj)
346 if obj_id in self.singleton_printers:
348 if obj_id in self.singleton_printers:
347 return self.singleton_printers[obj_id]
349 return self.singleton_printers[obj_id]
348 # then lookup by type
350 # then lookup by type
349 return self.lookup_by_type(_get_type(obj))
351 return self.lookup_by_type(_get_type(obj))
350
352
351 def lookup_by_type(self, typ):
353 def lookup_by_type(self, typ):
352 """Look up the registered formatter for a type.
354 """Look up the registered formatter for a type.
353
355
354 Parameters
356 Parameters
355 ----------
357 ----------
356 typ : type or '__module__.__name__' string for a type
358 typ : type or '__module__.__name__' string for a type
357
359
358 Returns
360 Returns
359 -------
361 -------
360 f : callable
362 f : callable
361 The registered formatting callable for the type.
363 The registered formatting callable for the type.
362
364
363 Raises
365 Raises
364 ------
366 ------
365 KeyError if the type has not been registered.
367 KeyError if the type has not been registered.
366 """
368 """
367 if isinstance(typ, string_types):
369 if isinstance(typ, string_types):
368 typ_key = tuple(typ.rsplit('.',1))
370 typ_key = tuple(typ.rsplit('.',1))
369 if typ_key not in self.deferred_printers:
371 if typ_key not in self.deferred_printers:
370 # We may have it cached in the type map. We will have to
372 # We may have it cached in the type map. We will have to
371 # iterate over all of the types to check.
373 # iterate over all of the types to check.
372 for cls in self.type_printers:
374 for cls in self.type_printers:
373 if _mod_name_key(cls) == typ_key:
375 if _mod_name_key(cls) == typ_key:
374 return self.type_printers[cls]
376 return self.type_printers[cls]
375 else:
377 else:
376 return self.deferred_printers[typ_key]
378 return self.deferred_printers[typ_key]
377 else:
379 else:
378 for cls in pretty._get_mro(typ):
380 for cls in pretty._get_mro(typ):
379 if cls in self.type_printers or self._in_deferred_types(cls):
381 if cls in self.type_printers or self._in_deferred_types(cls):
380 return self.type_printers[cls]
382 return self.type_printers[cls]
381
383
382 # If we have reached here, the lookup failed.
384 # If we have reached here, the lookup failed.
383 raise KeyError("No registered printer for {0!r}".format(typ))
385 raise KeyError("No registered printer for {0!r}".format(typ))
384
386
385 def for_type(self, typ, func=None):
387 def for_type(self, typ, func=None):
386 """Add a format function for a given type.
388 """Add a format function for a given type.
387
389
388 Parameters
390 Parameters
389 -----------
391 -----------
390 typ : type or '__module__.__name__' string for a type
392 typ : type or '__module__.__name__' string for a type
391 The class of the object that will be formatted using `func`.
393 The class of the object that will be formatted using `func`.
392 func : callable
394 func : callable
393 A callable for computing the format data.
395 A callable for computing the format data.
394 `func` will be called with the object to be formatted,
396 `func` will be called with the object to be formatted,
395 and will return the raw data in this formatter's format.
397 and will return the raw data in this formatter's format.
396 Subclasses may use a different call signature for the
398 Subclasses may use a different call signature for the
397 `func` argument.
399 `func` argument.
398
400
399 If `func` is None or not specified, there will be no change,
401 If `func` is None or not specified, there will be no change,
400 only returning the current value.
402 only returning the current value.
401
403
402 Returns
404 Returns
403 -------
405 -------
404 oldfunc : callable
406 oldfunc : callable
405 The currently registered callable.
407 The currently registered callable.
406 If you are registering a new formatter,
408 If you are registering a new formatter,
407 this will be the previous value (to enable restoring later).
409 this will be the previous value (to enable restoring later).
408 """
410 """
409 # if string given, interpret as 'pkg.module.class_name'
411 # if string given, interpret as 'pkg.module.class_name'
410 if isinstance(typ, string_types):
412 if isinstance(typ, string_types):
411 type_module, type_name = typ.rsplit('.', 1)
413 type_module, type_name = typ.rsplit('.', 1)
412 return self.for_type_by_name(type_module, type_name, func)
414 return self.for_type_by_name(type_module, type_name, func)
413
415
414 try:
416 try:
415 oldfunc = self.lookup_by_type(typ)
417 oldfunc = self.lookup_by_type(typ)
416 except KeyError:
418 except KeyError:
417 oldfunc = None
419 oldfunc = None
418
420
419 if func is not None:
421 if func is not None:
420 self.type_printers[typ] = func
422 self.type_printers[typ] = func
421
423
422 return oldfunc
424 return oldfunc
423
425
424 def for_type_by_name(self, type_module, type_name, func=None):
426 def for_type_by_name(self, type_module, type_name, func=None):
425 """Add a format function for a type specified by the full dotted
427 """Add a format function for a type specified by the full dotted
426 module and name of the type, rather than the type of the object.
428 module and name of the type, rather than the type of the object.
427
429
428 Parameters
430 Parameters
429 ----------
431 ----------
430 type_module : str
432 type_module : str
431 The full dotted name of the module the type is defined in, like
433 The full dotted name of the module the type is defined in, like
432 ``numpy``.
434 ``numpy``.
433 type_name : str
435 type_name : str
434 The name of the type (the class name), like ``dtype``
436 The name of the type (the class name), like ``dtype``
435 func : callable
437 func : callable
436 A callable for computing the format data.
438 A callable for computing the format data.
437 `func` will be called with the object to be formatted,
439 `func` will be called with the object to be formatted,
438 and will return the raw data in this formatter's format.
440 and will return the raw data in this formatter's format.
439 Subclasses may use a different call signature for the
441 Subclasses may use a different call signature for the
440 `func` argument.
442 `func` argument.
441
443
442 If `func` is None or unspecified, there will be no change,
444 If `func` is None or unspecified, there will be no change,
443 only returning the current value.
445 only returning the current value.
444
446
445 Returns
447 Returns
446 -------
448 -------
447 oldfunc : callable
449 oldfunc : callable
448 The currently registered callable.
450 The currently registered callable.
449 If you are registering a new formatter,
451 If you are registering a new formatter,
450 this will be the previous value (to enable restoring later).
452 this will be the previous value (to enable restoring later).
451 """
453 """
452 key = (type_module, type_name)
454 key = (type_module, type_name)
453
455
454 try:
456 try:
455 oldfunc = self.lookup_by_type("%s.%s" % key)
457 oldfunc = self.lookup_by_type("%s.%s" % key)
456 except KeyError:
458 except KeyError:
457 oldfunc = None
459 oldfunc = None
458
460
459 if func is not None:
461 if func is not None:
460 self.deferred_printers[key] = func
462 self.deferred_printers[key] = func
461 return oldfunc
463 return oldfunc
462
464
463 def pop(self, typ, default=_raise_key_error):
465 def pop(self, typ, default=_raise_key_error):
464 """Pop a formatter for the given type.
466 """Pop a formatter for the given type.
465
467
466 Parameters
468 Parameters
467 ----------
469 ----------
468 typ : type or '__module__.__name__' string for a type
470 typ : type or '__module__.__name__' string for a type
469 default : object
471 default : object
470 value to be returned if no formatter is registered for typ.
472 value to be returned if no formatter is registered for typ.
471
473
472 Returns
474 Returns
473 -------
475 -------
474 obj : object
476 obj : object
475 The last registered object for the type.
477 The last registered object for the type.
476
478
477 Raises
479 Raises
478 ------
480 ------
479 KeyError if the type is not registered and default is not specified.
481 KeyError if the type is not registered and default is not specified.
480 """
482 """
481
483
482 if isinstance(typ, string_types):
484 if isinstance(typ, string_types):
483 typ_key = tuple(typ.rsplit('.',1))
485 typ_key = tuple(typ.rsplit('.',1))
484 if typ_key not in self.deferred_printers:
486 if typ_key not in self.deferred_printers:
485 # We may have it cached in the type map. We will have to
487 # We may have it cached in the type map. We will have to
486 # iterate over all of the types to check.
488 # iterate over all of the types to check.
487 for cls in self.type_printers:
489 for cls in self.type_printers:
488 if _mod_name_key(cls) == typ_key:
490 if _mod_name_key(cls) == typ_key:
489 old = self.type_printers.pop(cls)
491 old = self.type_printers.pop(cls)
490 break
492 break
491 else:
493 else:
492 old = default
494 old = default
493 else:
495 else:
494 old = self.deferred_printers.pop(typ_key)
496 old = self.deferred_printers.pop(typ_key)
495 else:
497 else:
496 if typ in self.type_printers:
498 if typ in self.type_printers:
497 old = self.type_printers.pop(typ)
499 old = self.type_printers.pop(typ)
498 else:
500 else:
499 old = self.deferred_printers.pop(_mod_name_key(typ), default)
501 old = self.deferred_printers.pop(_mod_name_key(typ), default)
500 if old is _raise_key_error:
502 if old is _raise_key_error:
501 raise KeyError("No registered value for {0!r}".format(typ))
503 raise KeyError("No registered value for {0!r}".format(typ))
502 return old
504 return old
503
505
504 def _in_deferred_types(self, cls):
506 def _in_deferred_types(self, cls):
505 """
507 """
506 Check if the given class is specified in the deferred type registry.
508 Check if the given class is specified in the deferred type registry.
507
509
508 Successful matches will be moved to the regular type registry for future use.
510 Successful matches will be moved to the regular type registry for future use.
509 """
511 """
510 mod = getattr(cls, '__module__', None)
512 mod = getattr(cls, '__module__', None)
511 name = getattr(cls, '__name__', None)
513 name = getattr(cls, '__name__', None)
512 key = (mod, name)
514 key = (mod, name)
513 if key in self.deferred_printers:
515 if key in self.deferred_printers:
514 # Move the printer over to the regular registry.
516 # Move the printer over to the regular registry.
515 printer = self.deferred_printers.pop(key)
517 printer = self.deferred_printers.pop(key)
516 self.type_printers[cls] = printer
518 self.type_printers[cls] = printer
517 return True
519 return True
518 return False
520 return False
519
521
520
522
521 class PlainTextFormatter(BaseFormatter):
523 class PlainTextFormatter(BaseFormatter):
522 """The default pretty-printer.
524 """The default pretty-printer.
523
525
524 This uses :mod:`IPython.lib.pretty` to compute the format data of
526 This uses :mod:`IPython.lib.pretty` to compute the format data of
525 the object. If the object cannot be pretty printed, :func:`repr` is used.
527 the object. If the object cannot be pretty printed, :func:`repr` is used.
526 See the documentation of :mod:`IPython.lib.pretty` for details on
528 See the documentation of :mod:`IPython.lib.pretty` for details on
527 how to write pretty printers. Here is a simple example::
529 how to write pretty printers. Here is a simple example::
528
530
529 def dtype_pprinter(obj, p, cycle):
531 def dtype_pprinter(obj, p, cycle):
530 if cycle:
532 if cycle:
531 return p.text('dtype(...)')
533 return p.text('dtype(...)')
532 if hasattr(obj, 'fields'):
534 if hasattr(obj, 'fields'):
533 if obj.fields is None:
535 if obj.fields is None:
534 p.text(repr(obj))
536 p.text(repr(obj))
535 else:
537 else:
536 p.begin_group(7, 'dtype([')
538 p.begin_group(7, 'dtype([')
537 for i, field in enumerate(obj.descr):
539 for i, field in enumerate(obj.descr):
538 if i > 0:
540 if i > 0:
539 p.text(',')
541 p.text(',')
540 p.breakable()
542 p.breakable()
541 p.pretty(field)
543 p.pretty(field)
542 p.end_group(7, '])')
544 p.end_group(7, '])')
543 """
545 """
544
546
545 # The format type of data returned.
547 # The format type of data returned.
546 format_type = Unicode('text/plain')
548 format_type = Unicode('text/plain')
547
549
548 # This subclass ignores this attribute as it always need to return
550 # This subclass ignores this attribute as it always need to return
549 # something.
551 # something.
550 enabled = Bool(True, config=False)
552 enabled = Bool(True, config=False)
551
553
552 # Look for a _repr_pretty_ methods to use for pretty printing.
554 # Look for a _repr_pretty_ methods to use for pretty printing.
553 print_method = ObjectName('_repr_pretty_')
555 print_method = ObjectName('_repr_pretty_')
554
556
555 # Whether to pretty-print or not.
557 # Whether to pretty-print or not.
556 pprint = Bool(True, config=True)
558 pprint = Bool(True, config=True)
557
559
558 # Whether to be verbose or not.
560 # Whether to be verbose or not.
559 verbose = Bool(False, config=True)
561 verbose = Bool(False, config=True)
560
562
561 # The maximum width.
563 # The maximum width.
562 max_width = Integer(79, config=True)
564 max_width = Integer(79, config=True)
563
565
564 # The newline character.
566 # The newline character.
565 newline = Unicode('\n', config=True)
567 newline = Unicode('\n', config=True)
566
568
567 # format-string for pprinting floats
569 # format-string for pprinting floats
568 float_format = Unicode('%r')
570 float_format = Unicode('%r')
569 # setter for float precision, either int or direct format-string
571 # setter for float precision, either int or direct format-string
570 float_precision = CUnicode('', config=True)
572 float_precision = CUnicode('', config=True)
571
573
572 def _float_precision_changed(self, name, old, new):
574 def _float_precision_changed(self, name, old, new):
573 """float_precision changed, set float_format accordingly.
575 """float_precision changed, set float_format accordingly.
574
576
575 float_precision can be set by int or str.
577 float_precision can be set by int or str.
576 This will set float_format, after interpreting input.
578 This will set float_format, after interpreting input.
577 If numpy has been imported, numpy print precision will also be set.
579 If numpy has been imported, numpy print precision will also be set.
578
580
579 integer `n` sets format to '%.nf', otherwise, format set directly.
581 integer `n` sets format to '%.nf', otherwise, format set directly.
580
582
581 An empty string returns to defaults (repr for float, 8 for numpy).
583 An empty string returns to defaults (repr for float, 8 for numpy).
582
584
583 This parameter can be set via the '%precision' magic.
585 This parameter can be set via the '%precision' magic.
584 """
586 """
585
587
586 if '%' in new:
588 if '%' in new:
587 # got explicit format string
589 # got explicit format string
588 fmt = new
590 fmt = new
589 try:
591 try:
590 fmt%3.14159
592 fmt%3.14159
591 except Exception:
593 except Exception:
592 raise ValueError("Precision must be int or format string, not %r"%new)
594 raise ValueError("Precision must be int or format string, not %r"%new)
593 elif new:
595 elif new:
594 # otherwise, should be an int
596 # otherwise, should be an int
595 try:
597 try:
596 i = int(new)
598 i = int(new)
597 assert i >= 0
599 assert i >= 0
598 except ValueError:
600 except ValueError:
599 raise ValueError("Precision must be int or format string, not %r"%new)
601 raise ValueError("Precision must be int or format string, not %r"%new)
600 except AssertionError:
602 except AssertionError:
601 raise ValueError("int precision must be non-negative, not %r"%i)
603 raise ValueError("int precision must be non-negative, not %r"%i)
602
604
603 fmt = '%%.%if'%i
605 fmt = '%%.%if'%i
604 if 'numpy' in sys.modules:
606 if 'numpy' in sys.modules:
605 # set numpy precision if it has been imported
607 # set numpy precision if it has been imported
606 import numpy
608 import numpy
607 numpy.set_printoptions(precision=i)
609 numpy.set_printoptions(precision=i)
608 else:
610 else:
609 # default back to repr
611 # default back to repr
610 fmt = '%r'
612 fmt = '%r'
611 if 'numpy' in sys.modules:
613 if 'numpy' in sys.modules:
612 import numpy
614 import numpy
613 # numpy default is 8
615 # numpy default is 8
614 numpy.set_printoptions(precision=8)
616 numpy.set_printoptions(precision=8)
615 self.float_format = fmt
617 self.float_format = fmt
616
618
617 # Use the default pretty printers from IPython.lib.pretty.
619 # Use the default pretty printers from IPython.lib.pretty.
618 def _singleton_printers_default(self):
620 def _singleton_printers_default(self):
619 return pretty._singleton_pprinters.copy()
621 return pretty._singleton_pprinters.copy()
620
622
621 def _type_printers_default(self):
623 def _type_printers_default(self):
622 d = pretty._type_pprinters.copy()
624 d = pretty._type_pprinters.copy()
623 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
625 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
624 return d
626 return d
625
627
626 def _deferred_printers_default(self):
628 def _deferred_printers_default(self):
627 return pretty._deferred_type_pprinters.copy()
629 return pretty._deferred_type_pprinters.copy()
628
630
629 #### FormatterABC interface ####
631 #### FormatterABC interface ####
630
632
631 @warn_format_error
633 @warn_format_error
632 def __call__(self, obj):
634 def __call__(self, obj):
633 """Compute the pretty representation of the object."""
635 """Compute the pretty representation of the object."""
634 if not self.pprint:
636 if not self.pprint:
635 return pretty._safe_repr(obj)
637 return pretty._safe_repr(obj)
636 else:
638 else:
637 # This uses use StringIO, as cStringIO doesn't handle unicode.
639 # This uses use StringIO, as cStringIO doesn't handle unicode.
638 stream = StringIO()
640 stream = StringIO()
639 # self.newline.encode() is a quick fix for issue gh-597. We need to
641 # self.newline.encode() is a quick fix for issue gh-597. We need to
640 # ensure that stream does not get a mix of unicode and bytestrings,
642 # ensure that stream does not get a mix of unicode and bytestrings,
641 # or it will cause trouble.
643 # or it will cause trouble.
642 printer = pretty.RepresentationPrinter(stream, self.verbose,
644 printer = pretty.RepresentationPrinter(stream, self.verbose,
643 self.max_width, unicode_to_str(self.newline),
645 self.max_width, unicode_to_str(self.newline),
644 singleton_pprinters=self.singleton_printers,
646 singleton_pprinters=self.singleton_printers,
645 type_pprinters=self.type_printers,
647 type_pprinters=self.type_printers,
646 deferred_pprinters=self.deferred_printers)
648 deferred_pprinters=self.deferred_printers)
647 printer.pretty(obj)
649 printer.pretty(obj)
648 printer.flush()
650 printer.flush()
649 return stream.getvalue()
651 return stream.getvalue()
650
652
651
653
652 class HTMLFormatter(BaseFormatter):
654 class HTMLFormatter(BaseFormatter):
653 """An HTML formatter.
655 """An HTML formatter.
654
656
655 To define the callables that compute the HTML representation of your
657 To define the callables that compute the HTML representation of your
656 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
658 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
657 or :meth:`for_type_by_name` methods to register functions that handle
659 or :meth:`for_type_by_name` methods to register functions that handle
658 this.
660 this.
659
661
660 The return value of this formatter should be a valid HTML snippet that
662 The return value of this formatter should be a valid HTML snippet that
661 could be injected into an existing DOM. It should *not* include the
663 could be injected into an existing DOM. It should *not* include the
662 ```<html>`` or ```<body>`` tags.
664 ```<html>`` or ```<body>`` tags.
663 """
665 """
664 format_type = Unicode('text/html')
666 format_type = Unicode('text/html')
665
667
666 print_method = ObjectName('_repr_html_')
668 print_method = ObjectName('_repr_html_')
667
669
668
670
669 class SVGFormatter(BaseFormatter):
671 class SVGFormatter(BaseFormatter):
670 """An SVG formatter.
672 """An SVG formatter.
671
673
672 To define the callables that compute the SVG representation of your
674 To define the callables that compute the SVG representation of your
673 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
675 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
674 or :meth:`for_type_by_name` methods to register functions that handle
676 or :meth:`for_type_by_name` methods to register functions that handle
675 this.
677 this.
676
678
677 The return value of this formatter should be valid SVG enclosed in
679 The return value of this formatter should be valid SVG enclosed in
678 ```<svg>``` tags, that could be injected into an existing DOM. It should
680 ```<svg>``` tags, that could be injected into an existing DOM. It should
679 *not* include the ```<html>`` or ```<body>`` tags.
681 *not* include the ```<html>`` or ```<body>`` tags.
680 """
682 """
681 format_type = Unicode('image/svg+xml')
683 format_type = Unicode('image/svg+xml')
682
684
683 print_method = ObjectName('_repr_svg_')
685 print_method = ObjectName('_repr_svg_')
684
686
685
687
686 class PNGFormatter(BaseFormatter):
688 class PNGFormatter(BaseFormatter):
687 """A PNG formatter.
689 """A PNG formatter.
688
690
689 To define the callables that compute the PNG representation of your
691 To define the callables that compute the PNG representation of your
690 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
692 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
691 or :meth:`for_type_by_name` methods to register functions that handle
693 or :meth:`for_type_by_name` methods to register functions that handle
692 this.
694 this.
693
695
694 The return value of this formatter should be raw PNG data, *not*
696 The return value of this formatter should be raw PNG data, *not*
695 base64 encoded.
697 base64 encoded.
696 """
698 """
697 format_type = Unicode('image/png')
699 format_type = Unicode('image/png')
698
700
699 print_method = ObjectName('_repr_png_')
701 print_method = ObjectName('_repr_png_')
700
702
701 _return_type = (bytes, unicode_type)
703 _return_type = (bytes, unicode_type)
702
704
703
705
704 class JPEGFormatter(BaseFormatter):
706 class JPEGFormatter(BaseFormatter):
705 """A JPEG formatter.
707 """A JPEG formatter.
706
708
707 To define the callables that compute the JPEG representation of your
709 To define the callables that compute the JPEG representation of your
708 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
710 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
709 or :meth:`for_type_by_name` methods to register functions that handle
711 or :meth:`for_type_by_name` methods to register functions that handle
710 this.
712 this.
711
713
712 The return value of this formatter should be raw JPEG data, *not*
714 The return value of this formatter should be raw JPEG data, *not*
713 base64 encoded.
715 base64 encoded.
714 """
716 """
715 format_type = Unicode('image/jpeg')
717 format_type = Unicode('image/jpeg')
716
718
717 print_method = ObjectName('_repr_jpeg_')
719 print_method = ObjectName('_repr_jpeg_')
718
720
719 _return_type = (bytes, unicode_type)
721 _return_type = (bytes, unicode_type)
720
722
721
723
722 class LatexFormatter(BaseFormatter):
724 class LatexFormatter(BaseFormatter):
723 """A LaTeX formatter.
725 """A LaTeX formatter.
724
726
725 To define the callables that compute the LaTeX representation of your
727 To define the callables that compute the LaTeX representation of your
726 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
728 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
727 or :meth:`for_type_by_name` methods to register functions that handle
729 or :meth:`for_type_by_name` methods to register functions that handle
728 this.
730 this.
729
731
730 The return value of this formatter should be a valid LaTeX equation,
732 The return value of this formatter should be a valid LaTeX equation,
731 enclosed in either ```$```, ```$$``` or another LaTeX equation
733 enclosed in either ```$```, ```$$``` or another LaTeX equation
732 environment.
734 environment.
733 """
735 """
734 format_type = Unicode('text/latex')
736 format_type = Unicode('text/latex')
735
737
736 print_method = ObjectName('_repr_latex_')
738 print_method = ObjectName('_repr_latex_')
737
739
738
740
739 class JSONFormatter(BaseFormatter):
741 class JSONFormatter(BaseFormatter):
740 """A JSON string formatter.
742 """A JSON string formatter.
741
743
742 To define the callables that compute the JSON string representation of
744 To define the callables that compute the JSON string representation of
743 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
745 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
744 or :meth:`for_type_by_name` methods to register functions that handle
746 or :meth:`for_type_by_name` methods to register functions that handle
745 this.
747 this.
746
748
747 The return value of this formatter should be a valid JSON string.
749 The return value of this formatter should be a valid JSON string.
748 """
750 """
749 format_type = Unicode('application/json')
751 format_type = Unicode('application/json')
750
752
751 print_method = ObjectName('_repr_json_')
753 print_method = ObjectName('_repr_json_')
752
754
753
755
754 class JavascriptFormatter(BaseFormatter):
756 class JavascriptFormatter(BaseFormatter):
755 """A Javascript formatter.
757 """A Javascript formatter.
756
758
757 To define the callables that compute the Javascript representation of
759 To define the callables that compute the Javascript representation of
758 your objects, define a :meth:`_repr_javascript_` method or use the
760 your objects, define a :meth:`_repr_javascript_` method or use the
759 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
761 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
760 that handle this.
762 that handle this.
761
763
762 The return value of this formatter should be valid Javascript code and
764 The return value of this formatter should be valid Javascript code and
763 should *not* be enclosed in ```<script>``` tags.
765 should *not* be enclosed in ```<script>``` tags.
764 """
766 """
765 format_type = Unicode('application/javascript')
767 format_type = Unicode('application/javascript')
766
768
767 print_method = ObjectName('_repr_javascript_')
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 FormatterABC.register(BaseFormatter)
788 FormatterABC.register(BaseFormatter)
770 FormatterABC.register(PlainTextFormatter)
789 FormatterABC.register(PlainTextFormatter)
771 FormatterABC.register(HTMLFormatter)
790 FormatterABC.register(HTMLFormatter)
772 FormatterABC.register(SVGFormatter)
791 FormatterABC.register(SVGFormatter)
773 FormatterABC.register(PNGFormatter)
792 FormatterABC.register(PNGFormatter)
793 FormatterABC.register(PDFFormatter)
774 FormatterABC.register(JPEGFormatter)
794 FormatterABC.register(JPEGFormatter)
775 FormatterABC.register(LatexFormatter)
795 FormatterABC.register(LatexFormatter)
776 FormatterABC.register(JSONFormatter)
796 FormatterABC.register(JSONFormatter)
777 FormatterABC.register(JavascriptFormatter)
797 FormatterABC.register(JavascriptFormatter)
778
798
779
799
780 def format_display_data(obj, include=None, exclude=None):
800 def format_display_data(obj, include=None, exclude=None):
781 """Return a format data dict for an object.
801 """Return a format data dict for an object.
782
802
783 By default all format types will be computed.
803 By default all format types will be computed.
784
804
785 The following MIME types are currently implemented:
805 The following MIME types are currently implemented:
786
806
787 * text/plain
807 * text/plain
788 * text/html
808 * text/html
789 * text/latex
809 * text/latex
790 * application/json
810 * application/json
791 * application/javascript
811 * application/javascript
812 * application/pdf
792 * image/png
813 * image/png
793 * image/jpeg
814 * image/jpeg
794 * image/svg+xml
815 * image/svg+xml
795
816
796 Parameters
817 Parameters
797 ----------
818 ----------
798 obj : object
819 obj : object
799 The Python object whose format data will be computed.
820 The Python object whose format data will be computed.
800
821
801 Returns
822 Returns
802 -------
823 -------
803 format_dict : dict
824 format_dict : dict
804 A dictionary of key/value pairs, one or each format that was
825 A dictionary of key/value pairs, one or each format that was
805 generated for the object. The keys are the format types, which
826 generated for the object. The keys are the format types, which
806 will usually be MIME type strings and the values and JSON'able
827 will usually be MIME type strings and the values and JSON'able
807 data structure containing the raw data for the representation in
828 data structure containing the raw data for the representation in
808 that format.
829 that format.
809 include : list or tuple, optional
830 include : list or tuple, optional
810 A list of format type strings (MIME types) to include in the
831 A list of format type strings (MIME types) to include in the
811 format data dict. If this is set *only* the format types included
832 format data dict. If this is set *only* the format types included
812 in this list will be computed.
833 in this list will be computed.
813 exclude : list or tuple, optional
834 exclude : list or tuple, optional
814 A list of format type string (MIME types) to exclue in the format
835 A list of format type string (MIME types) to exclue in the format
815 data dict. If this is set all format types will be computed,
836 data dict. If this is set all format types will be computed,
816 except for those included in this argument.
837 except for those included in this argument.
817 """
838 """
818 from IPython.core.interactiveshell import InteractiveShell
839 from IPython.core.interactiveshell import InteractiveShell
819
840
820 InteractiveShell.instance().display_formatter.format(
841 InteractiveShell.instance().display_formatter.format(
821 obj,
842 obj,
822 include,
843 include,
823 exclude
844 exclude
824 )
845 )
825
846
@@ -1,143 +1,147 b''
1 """Implementation of magic functions for matplotlib/pylab support.
1 """Implementation of magic functions for matplotlib/pylab support.
2 """
2 """
3 from __future__ import print_function
3 from __future__ import print_function
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2012 The IPython Development Team.
5 # Copyright (c) 2012 The IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 # Our own packages
16 # Our own packages
17 from IPython.config.application import Application
17 from IPython.config.application import Application
18 from IPython.core import magic_arguments
18 from IPython.core import magic_arguments
19 from IPython.core.magic import Magics, magics_class, line_magic
19 from IPython.core.magic import Magics, magics_class, line_magic
20 from IPython.testing.skipdoctest import skip_doctest
20 from IPython.testing.skipdoctest import skip_doctest
21 from IPython.utils.warn import warn
21 from IPython.utils.warn import warn
22 from IPython.core.pylabtools import backends
22 from IPython.core.pylabtools import backends
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Magic implementation classes
25 # Magic implementation classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 magic_gui_arg = magic_arguments.argument(
28 magic_gui_arg = magic_arguments.argument(
29 'gui', nargs='?',
29 'gui', nargs='?',
30 help="""Name of the matplotlib backend to use %s.
30 help="""Name of the matplotlib backend to use %s.
31 If given, the corresponding matplotlib backend is used,
31 If given, the corresponding matplotlib backend is used,
32 otherwise it will be matplotlib's default
32 otherwise it will be matplotlib's default
33 (which you can set in your matplotlib config file).
33 (which you can set in your matplotlib config file).
34 """ % str(tuple(sorted(backends.keys())))
34 """ % str(tuple(sorted(backends.keys())))
35 )
35 )
36
36
37
37
38 @magics_class
38 @magics_class
39 class PylabMagics(Magics):
39 class PylabMagics(Magics):
40 """Magics related to matplotlib's pylab support"""
40 """Magics related to matplotlib's pylab support"""
41
41
42 @skip_doctest
42 @skip_doctest
43 @line_magic
43 @line_magic
44 @magic_arguments.magic_arguments()
44 @magic_arguments.magic_arguments()
45 @magic_gui_arg
45 @magic_gui_arg
46 def matplotlib(self, line=''):
46 def matplotlib(self, line=''):
47 """Set up matplotlib to work interactively.
47 """Set up matplotlib to work interactively.
48
48
49 This function lets you activate matplotlib interactive support
49 This function lets you activate matplotlib interactive support
50 at any point during an IPython session.
50 at any point during an IPython session. It does not import anything
51 It does not import anything into the interactive namespace.
51 into the interactive namespace.
52
52
53 If you are using the inline matplotlib backend for embedded figures,
53 If you are using the inline matplotlib backend in the IPython Notebook
54 you can adjust its behavior via the %config magic::
54 you can set which figure formats are enabled using the following::
55
55
56 # enable SVG figures, necessary for SVG+XHTML export in the qtconsole
56 In [1]: from IPython.display import set_matplotlib_formats
57 In [1]: %config InlineBackend.figure_format = 'svg'
57
58 In [2]: set_matplotlib_formats('pdf', 'svg')
58
59
59 # change the behavior of closing all figures at the end of each
60 See the docstring of `IPython.display.set_matplotlib_formats` and
60 # execution (cell), or allowing reuse of active figures across
61 `IPython.display.set_matplotlib_close` for more information on
61 # cells:
62 changing the behavior of the inline backend.
62 In [2]: %config InlineBackend.close_figures = False
63
63
64 Examples
64 Examples
65 --------
65 --------
66 In this case, where the MPL default is TkAgg::
66 To enable the inline backend for usage with the IPython Notebook::
67
68 In [1]: %matplotlib inline
69
70 In this case, where the matplotlib default is TkAgg::
67
71
68 In [2]: %matplotlib
72 In [2]: %matplotlib
69 Using matplotlib backend: TkAgg
73 Using matplotlib backend: TkAgg
70
74
71 But you can explicitly request a different backend::
75 But you can explicitly request a different GUI backend::
72
76
73 In [3]: %matplotlib qt
77 In [3]: %matplotlib qt
74 """
78 """
75 args = magic_arguments.parse_argstring(self.matplotlib, line)
79 args = magic_arguments.parse_argstring(self.matplotlib, line)
76 gui, backend = self.shell.enable_matplotlib(args.gui)
80 gui, backend = self.shell.enable_matplotlib(args.gui)
77 self._show_matplotlib_backend(args.gui, backend)
81 self._show_matplotlib_backend(args.gui, backend)
78
82
79 @skip_doctest
83 @skip_doctest
80 @line_magic
84 @line_magic
81 @magic_arguments.magic_arguments()
85 @magic_arguments.magic_arguments()
82 @magic_arguments.argument(
86 @magic_arguments.argument(
83 '--no-import-all', action='store_true', default=None,
87 '--no-import-all', action='store_true', default=None,
84 help="""Prevent IPython from performing ``import *`` into the interactive namespace.
88 help="""Prevent IPython from performing ``import *`` into the interactive namespace.
85
89
86 You can govern the default behavior of this flag with the
90 You can govern the default behavior of this flag with the
87 InteractiveShellApp.pylab_import_all configurable.
91 InteractiveShellApp.pylab_import_all configurable.
88 """
92 """
89 )
93 )
90 @magic_gui_arg
94 @magic_gui_arg
91 def pylab(self, line=''):
95 def pylab(self, line=''):
92 """Load numpy and matplotlib to work interactively.
96 """Load numpy and matplotlib to work interactively.
93
97
94 This function lets you activate pylab (matplotlib, numpy and
98 This function lets you activate pylab (matplotlib, numpy and
95 interactive support) at any point during an IPython session.
99 interactive support) at any point during an IPython session.
96
100
97 %pylab makes the following imports::
101 %pylab makes the following imports::
98
102
99 import numpy
103 import numpy
100 import matplotlib
104 import matplotlib
101 from matplotlib import pylab, mlab, pyplot
105 from matplotlib import pylab, mlab, pyplot
102 np = numpy
106 np = numpy
103 plt = pyplot
107 plt = pyplot
104
108
105 from IPython.display import display
109 from IPython.display import display
106 from IPython.core.pylabtools import figsize, getfigs
110 from IPython.core.pylabtools import figsize, getfigs
107
111
108 from pylab import *
112 from pylab import *
109 from numpy import *
113 from numpy import *
110
114
111 If you pass `--no-import-all`, the last two `*` imports will be excluded.
115 If you pass `--no-import-all`, the last two `*` imports will be excluded.
112
116
113 See the %matplotlib magic for more details about activating matplotlib
117 See the %matplotlib magic for more details about activating matplotlib
114 without affecting the interactive namespace.
118 without affecting the interactive namespace.
115 """
119 """
116 args = magic_arguments.parse_argstring(self.pylab, line)
120 args = magic_arguments.parse_argstring(self.pylab, line)
117 if args.no_import_all is None:
121 if args.no_import_all is None:
118 # get default from Application
122 # get default from Application
119 if Application.initialized():
123 if Application.initialized():
120 app = Application.instance()
124 app = Application.instance()
121 try:
125 try:
122 import_all = app.pylab_import_all
126 import_all = app.pylab_import_all
123 except AttributeError:
127 except AttributeError:
124 import_all = True
128 import_all = True
125 else:
129 else:
126 # nothing specified, no app - default True
130 # nothing specified, no app - default True
127 import_all = True
131 import_all = True
128 else:
132 else:
129 # invert no-import flag
133 # invert no-import flag
130 import_all = not args.no_import_all
134 import_all = not args.no_import_all
131
135
132 gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all)
136 gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all)
133 self._show_matplotlib_backend(args.gui, backend)
137 self._show_matplotlib_backend(args.gui, backend)
134 print ("Populating the interactive namespace from numpy and matplotlib")
138 print ("Populating the interactive namespace from numpy and matplotlib")
135 if clobbered:
139 if clobbered:
136 warn("pylab import has clobbered these variables: %s" % clobbered +
140 warn("pylab import has clobbered these variables: %s" % clobbered +
137 "\n`%pylab --no-import-all` prevents importing * from pylab and numpy"
141 "\n`%pylab --no-import-all` prevents importing * from pylab and numpy"
138 )
142 )
139
143
140 def _show_matplotlib_backend(self, gui, backend):
144 def _show_matplotlib_backend(self, gui, backend):
141 """show matplotlib message backend message"""
145 """show matplotlib message backend message"""
142 if not gui or gui == 'auto':
146 if not gui or gui == 'auto':
143 print("Using matplotlib backend: %s" % backend)
147 print("Using matplotlib backend: %s" % backend)
@@ -1,346 +1,358 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Pylab (matplotlib) support utilities.
2 """Pylab (matplotlib) support utilities.
3
3
4 Authors
4 Authors
5 -------
5 -------
6
6
7 * Fernando Perez.
7 * Fernando Perez.
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2009 The IPython Development Team
13 # Copyright (C) 2009 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import sys
23 import sys
24 from io import BytesIO
24 from io import BytesIO
25
25
26 from IPython.core.display import _pngxy
26 from IPython.core.display import _pngxy
27 from IPython.utils.decorators import flag_calls
27 from IPython.utils.decorators import flag_calls
28 from IPython.utils import py3compat
28
29
29 # If user specifies a GUI, that dictates the backend, otherwise we read the
30 # If user specifies a GUI, that dictates the backend, otherwise we read the
30 # user's mpl default from the mpl rc structure
31 # user's mpl default from the mpl rc structure
31 backends = {'tk': 'TkAgg',
32 backends = {'tk': 'TkAgg',
32 'gtk': 'GTKAgg',
33 'gtk': 'GTKAgg',
33 'gtk3': 'GTK3Agg',
34 'gtk3': 'GTK3Agg',
34 'wx': 'WXAgg',
35 'wx': 'WXAgg',
35 'qt': 'Qt4Agg', # qt3 not supported
36 'qt': 'Qt4Agg', # qt3 not supported
36 'qt4': 'Qt4Agg',
37 'qt4': 'Qt4Agg',
37 'osx': 'MacOSX',
38 'osx': 'MacOSX',
38 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
39 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
39
40
40 # We also need a reverse backends2guis mapping that will properly choose which
41 # We also need a reverse backends2guis mapping that will properly choose which
41 # GUI support to activate based on the desired matplotlib backend. For the
42 # GUI support to activate based on the desired matplotlib backend. For the
42 # most part it's just a reverse of the above dict, but we also need to add a
43 # most part it's just a reverse of the above dict, but we also need to add a
43 # few others that map to the same GUI manually:
44 # few others that map to the same GUI manually:
44 backend2gui = dict(zip(backends.values(), backends.keys()))
45 backend2gui = dict(zip(backends.values(), backends.keys()))
45 # Our tests expect backend2gui to just return 'qt'
46 # Our tests expect backend2gui to just return 'qt'
46 backend2gui['Qt4Agg'] = 'qt'
47 backend2gui['Qt4Agg'] = 'qt'
47 # In the reverse mapping, there are a few extra valid matplotlib backends that
48 # In the reverse mapping, there are a few extra valid matplotlib backends that
48 # map to the same GUI support
49 # map to the same GUI support
49 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
50 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
50 backend2gui['GTK3Cairo'] = 'gtk3'
51 backend2gui['GTK3Cairo'] = 'gtk3'
51 backend2gui['WX'] = 'wx'
52 backend2gui['WX'] = 'wx'
52 backend2gui['CocoaAgg'] = 'osx'
53 backend2gui['CocoaAgg'] = 'osx'
53
54
54 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
55 # Matplotlib utilities
56 # Matplotlib utilities
56 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
57
58
58
59
59 def getfigs(*fig_nums):
60 def getfigs(*fig_nums):
60 """Get a list of matplotlib figures by figure numbers.
61 """Get a list of matplotlib figures by figure numbers.
61
62
62 If no arguments are given, all available figures are returned. If the
63 If no arguments are given, all available figures are returned. If the
63 argument list contains references to invalid figures, a warning is printed
64 argument list contains references to invalid figures, a warning is printed
64 but the function continues pasting further figures.
65 but the function continues pasting further figures.
65
66
66 Parameters
67 Parameters
67 ----------
68 ----------
68 figs : tuple
69 figs : tuple
69 A tuple of ints giving the figure numbers of the figures to return.
70 A tuple of ints giving the figure numbers of the figures to return.
70 """
71 """
71 from matplotlib._pylab_helpers import Gcf
72 from matplotlib._pylab_helpers import Gcf
72 if not fig_nums:
73 if not fig_nums:
73 fig_managers = Gcf.get_all_fig_managers()
74 fig_managers = Gcf.get_all_fig_managers()
74 return [fm.canvas.figure for fm in fig_managers]
75 return [fm.canvas.figure for fm in fig_managers]
75 else:
76 else:
76 figs = []
77 figs = []
77 for num in fig_nums:
78 for num in fig_nums:
78 f = Gcf.figs.get(num)
79 f = Gcf.figs.get(num)
79 if f is None:
80 if f is None:
80 print('Warning: figure %s not available.' % num)
81 print('Warning: figure %s not available.' % num)
81 else:
82 else:
82 figs.append(f.canvas.figure)
83 figs.append(f.canvas.figure)
83 return figs
84 return figs
84
85
85
86
86 def figsize(sizex, sizey):
87 def figsize(sizex, sizey):
87 """Set the default figure size to be [sizex, sizey].
88 """Set the default figure size to be [sizex, sizey].
88
89
89 This is just an easy to remember, convenience wrapper that sets::
90 This is just an easy to remember, convenience wrapper that sets::
90
91
91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 """
93 """
93 import matplotlib
94 import matplotlib
94 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95
96
96
97
97 def print_figure(fig, fmt='png', quality=90):
98 def print_figure(fig, fmt='png', quality=90):
98 """Convert a figure to svg, png or jpg for inline display.
99 """Convert a figure to svg, png or jpg for inline display.
99 Quality is only relevant for jpg.
100 Quality is only relevant for jpg.
100 """
101 """
101 from matplotlib import rcParams
102 from matplotlib import rcParams
102 # When there's an empty figure, we shouldn't return anything, otherwise we
103 # When there's an empty figure, we shouldn't return anything, otherwise we
103 # get big blank areas in the qt console.
104 # get big blank areas in the qt console.
104 if not fig.axes and not fig.lines:
105 if not fig.axes and not fig.lines:
105 return
106 return
106
107
107 fc = fig.get_facecolor()
108 fc = fig.get_facecolor()
108 ec = fig.get_edgecolor()
109 ec = fig.get_edgecolor()
109 bytes_io = BytesIO()
110 bytes_io = BytesIO()
110 dpi = rcParams['savefig.dpi']
111 dpi = rcParams['savefig.dpi']
111 if fmt == 'retina':
112 if fmt == 'retina':
112 dpi = dpi * 2
113 dpi = dpi * 2
113 fmt = 'png'
114 fmt = 'png'
114 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
115 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
115 facecolor=fc, edgecolor=ec, dpi=dpi, quality=quality)
116 facecolor=fc, edgecolor=ec, dpi=dpi, quality=quality)
116 data = bytes_io.getvalue()
117 data = bytes_io.getvalue()
117 return data
118 return data
118
119
119 def retina_figure(fig):
120 def retina_figure(fig):
120 """format a figure as a pixel-doubled (retina) PNG"""
121 """format a figure as a pixel-doubled (retina) PNG"""
121 pngdata = print_figure(fig, fmt='retina')
122 pngdata = print_figure(fig, fmt='retina')
122 w, h = _pngxy(pngdata)
123 w, h = _pngxy(pngdata)
123 metadata = dict(width=w//2, height=h//2)
124 metadata = dict(width=w//2, height=h//2)
124 return pngdata, metadata
125 return pngdata, metadata
125
126
126 # We need a little factory function here to create the closure where
127 # We need a little factory function here to create the closure where
127 # safe_execfile can live.
128 # safe_execfile can live.
128 def mpl_runner(safe_execfile):
129 def mpl_runner(safe_execfile):
129 """Factory to return a matplotlib-enabled runner for %run.
130 """Factory to return a matplotlib-enabled runner for %run.
130
131
131 Parameters
132 Parameters
132 ----------
133 ----------
133 safe_execfile : function
134 safe_execfile : function
134 This must be a function with the same interface as the
135 This must be a function with the same interface as the
135 :meth:`safe_execfile` method of IPython.
136 :meth:`safe_execfile` method of IPython.
136
137
137 Returns
138 Returns
138 -------
139 -------
139 A function suitable for use as the ``runner`` argument of the %run magic
140 A function suitable for use as the ``runner`` argument of the %run magic
140 function.
141 function.
141 """
142 """
142
143
143 def mpl_execfile(fname,*where,**kw):
144 def mpl_execfile(fname,*where,**kw):
144 """matplotlib-aware wrapper around safe_execfile.
145 """matplotlib-aware wrapper around safe_execfile.
145
146
146 Its interface is identical to that of the :func:`execfile` builtin.
147 Its interface is identical to that of the :func:`execfile` builtin.
147
148
148 This is ultimately a call to execfile(), but wrapped in safeties to
149 This is ultimately a call to execfile(), but wrapped in safeties to
149 properly handle interactive rendering."""
150 properly handle interactive rendering."""
150
151
151 import matplotlib
152 import matplotlib
152 import matplotlib.pylab as pylab
153 import matplotlib.pylab as pylab
153
154
154 #print '*** Matplotlib runner ***' # dbg
155 #print '*** Matplotlib runner ***' # dbg
155 # turn off rendering until end of script
156 # turn off rendering until end of script
156 is_interactive = matplotlib.rcParams['interactive']
157 is_interactive = matplotlib.rcParams['interactive']
157 matplotlib.interactive(False)
158 matplotlib.interactive(False)
158 safe_execfile(fname,*where,**kw)
159 safe_execfile(fname,*where,**kw)
159 matplotlib.interactive(is_interactive)
160 matplotlib.interactive(is_interactive)
160 # make rendering call now, if the user tried to do it
161 # make rendering call now, if the user tried to do it
161 if pylab.draw_if_interactive.called:
162 if pylab.draw_if_interactive.called:
162 pylab.draw()
163 pylab.draw()
163 pylab.draw_if_interactive.called = False
164 pylab.draw_if_interactive.called = False
164
165
165 return mpl_execfile
166 return mpl_execfile
166
167
167
168
168 def select_figure_format(shell, fmt, quality=90):
169 def select_figure_formats(shell, formats, quality=90):
169 """Select figure format for inline backend, can be 'png', 'retina', 'jpg', or 'svg'.
170 """Select figure formats for the inline backend.
170
171
171 Using this method ensures only one figure format is active at a time.
172 Parameters
173 ==========
174 shell : InteractiveShell
175 The main IPython instance.
176 formats : list
177 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
178 quality : int
179 A percentage for the quality of JPEG figures.
172 """
180 """
173 from matplotlib.figure import Figure
181 from matplotlib.figure import Figure
174 from IPython.kernel.zmq.pylab import backend_inline
182 from IPython.kernel.zmq.pylab import backend_inline
175
183
176 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
184 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
177 png_formatter = shell.display_formatter.formatters['image/png']
185 png_formatter = shell.display_formatter.formatters['image/png']
178 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
186 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
187 pdf_formatter = shell.display_formatter.formatters['application/pdf']
179
188
180 [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ]
189 if isinstance(formats, py3compat.string_types):
190 formats = {formats}
181
191
182 if fmt == 'png':
192 [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ]
183 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
184 elif fmt in ('png2x', 'retina'):
185 png_formatter.for_type(Figure, retina_figure)
186 elif fmt in ('jpg', 'jpeg'):
187 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality))
188 elif fmt == 'svg':
189 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
190 else:
191 raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', not %r" % fmt)
192
193
193 # set the format to be used in the backend()
194 for fmt in formats:
194 backend_inline._figure_format = fmt
195 if fmt == 'png':
196 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
197 elif fmt in ('png2x', 'retina'):
198 png_formatter.for_type(Figure, retina_figure)
199 elif fmt in ('jpg', 'jpeg'):
200 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality))
201 elif fmt == 'svg':
202 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
203 elif fmt == 'pdf':
204 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf'))
205 else:
206 raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', 'pdf' not %r" % fmt)
195
207
196 #-----------------------------------------------------------------------------
208 #-----------------------------------------------------------------------------
197 # Code for initializing matplotlib and importing pylab
209 # Code for initializing matplotlib and importing pylab
198 #-----------------------------------------------------------------------------
210 #-----------------------------------------------------------------------------
199
211
200
212
201 def find_gui_and_backend(gui=None, gui_select=None):
213 def find_gui_and_backend(gui=None, gui_select=None):
202 """Given a gui string return the gui and mpl backend.
214 """Given a gui string return the gui and mpl backend.
203
215
204 Parameters
216 Parameters
205 ----------
217 ----------
206 gui : str
218 gui : str
207 Can be one of ('tk','gtk','wx','qt','qt4','inline').
219 Can be one of ('tk','gtk','wx','qt','qt4','inline').
208 gui_select : str
220 gui_select : str
209 Can be one of ('tk','gtk','wx','qt','qt4','inline').
221 Can be one of ('tk','gtk','wx','qt','qt4','inline').
210 This is any gui already selected by the shell.
222 This is any gui already selected by the shell.
211
223
212 Returns
224 Returns
213 -------
225 -------
214 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
226 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
215 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
227 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
216 """
228 """
217
229
218 import matplotlib
230 import matplotlib
219
231
220 if gui and gui != 'auto':
232 if gui and gui != 'auto':
221 # select backend based on requested gui
233 # select backend based on requested gui
222 backend = backends[gui]
234 backend = backends[gui]
223 else:
235 else:
224 # We need to read the backend from the original data structure, *not*
236 # We need to read the backend from the original data structure, *not*
225 # from mpl.rcParams, since a prior invocation of %matplotlib may have
237 # from mpl.rcParams, since a prior invocation of %matplotlib may have
226 # overwritten that.
238 # overwritten that.
227 # WARNING: this assumes matplotlib 1.1 or newer!!
239 # WARNING: this assumes matplotlib 1.1 or newer!!
228 backend = matplotlib.rcParamsOrig['backend']
240 backend = matplotlib.rcParamsOrig['backend']
229 # In this case, we need to find what the appropriate gui selection call
241 # In this case, we need to find what the appropriate gui selection call
230 # should be for IPython, so we can activate inputhook accordingly
242 # should be for IPython, so we can activate inputhook accordingly
231 gui = backend2gui.get(backend, None)
243 gui = backend2gui.get(backend, None)
232
244
233 # If we have already had a gui active, we need it and inline are the
245 # If we have already had a gui active, we need it and inline are the
234 # ones allowed.
246 # ones allowed.
235 if gui_select and gui != gui_select:
247 if gui_select and gui != gui_select:
236 gui = gui_select
248 gui = gui_select
237 backend = backends[gui]
249 backend = backends[gui]
238
250
239 return gui, backend
251 return gui, backend
240
252
241
253
242 def activate_matplotlib(backend):
254 def activate_matplotlib(backend):
243 """Activate the given backend and set interactive to True."""
255 """Activate the given backend and set interactive to True."""
244
256
245 import matplotlib
257 import matplotlib
246 matplotlib.interactive(True)
258 matplotlib.interactive(True)
247
259
248 # Matplotlib had a bug where even switch_backend could not force
260 # Matplotlib had a bug where even switch_backend could not force
249 # the rcParam to update. This needs to be set *before* the module
261 # the rcParam to update. This needs to be set *before* the module
250 # magic of switch_backend().
262 # magic of switch_backend().
251 matplotlib.rcParams['backend'] = backend
263 matplotlib.rcParams['backend'] = backend
252
264
253 import matplotlib.pyplot
265 import matplotlib.pyplot
254 matplotlib.pyplot.switch_backend(backend)
266 matplotlib.pyplot.switch_backend(backend)
255
267
256 # This must be imported last in the matplotlib series, after
268 # This must be imported last in the matplotlib series, after
257 # backend/interactivity choices have been made
269 # backend/interactivity choices have been made
258 import matplotlib.pylab as pylab
270 import matplotlib.pylab as pylab
259
271
260 pylab.show._needmain = False
272 pylab.show._needmain = False
261 # We need to detect at runtime whether show() is called by the user.
273 # We need to detect at runtime whether show() is called by the user.
262 # For this, we wrap it into a decorator which adds a 'called' flag.
274 # For this, we wrap it into a decorator which adds a 'called' flag.
263 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
275 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
264
276
265
277
266 def import_pylab(user_ns, import_all=True):
278 def import_pylab(user_ns, import_all=True):
267 """Populate the namespace with pylab-related values.
279 """Populate the namespace with pylab-related values.
268
280
269 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
281 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
270
282
271 Also imports a few names from IPython (figsize, display, getfigs)
283 Also imports a few names from IPython (figsize, display, getfigs)
272
284
273 """
285 """
274
286
275 # Import numpy as np/pyplot as plt are conventions we're trying to
287 # Import numpy as np/pyplot as plt are conventions we're trying to
276 # somewhat standardize on. Making them available to users by default
288 # somewhat standardize on. Making them available to users by default
277 # will greatly help this.
289 # will greatly help this.
278 s = ("import numpy\n"
290 s = ("import numpy\n"
279 "import matplotlib\n"
291 "import matplotlib\n"
280 "from matplotlib import pylab, mlab, pyplot\n"
292 "from matplotlib import pylab, mlab, pyplot\n"
281 "np = numpy\n"
293 "np = numpy\n"
282 "plt = pyplot\n"
294 "plt = pyplot\n"
283 )
295 )
284 exec(s, user_ns)
296 exec(s, user_ns)
285
297
286 if import_all:
298 if import_all:
287 s = ("from matplotlib.pylab import *\n"
299 s = ("from matplotlib.pylab import *\n"
288 "from numpy import *\n")
300 "from numpy import *\n")
289 exec(s, user_ns)
301 exec(s, user_ns)
290
302
291 # IPython symbols to add
303 # IPython symbols to add
292 user_ns['figsize'] = figsize
304 user_ns['figsize'] = figsize
293 from IPython.core.display import display
305 from IPython.core.display import display
294 # Add display and getfigs to the user's namespace
306 # Add display and getfigs to the user's namespace
295 user_ns['display'] = display
307 user_ns['display'] = display
296 user_ns['getfigs'] = getfigs
308 user_ns['getfigs'] = getfigs
297
309
298
310
299 def configure_inline_support(shell, backend):
311 def configure_inline_support(shell, backend):
300 """Configure an IPython shell object for matplotlib use.
312 """Configure an IPython shell object for matplotlib use.
301
313
302 Parameters
314 Parameters
303 ----------
315 ----------
304 shell : InteractiveShell instance
316 shell : InteractiveShell instance
305
317
306 backend : matplotlib backend
318 backend : matplotlib backend
307 """
319 """
308 # If using our svg payload backend, register the post-execution
320 # If using our svg payload backend, register the post-execution
309 # function that will pick up the results for display. This can only be
321 # function that will pick up the results for display. This can only be
310 # done with access to the real shell object.
322 # done with access to the real shell object.
311
323
312 # Note: if we can't load the inline backend, then there's no point
324 # Note: if we can't load the inline backend, then there's no point
313 # continuing (such as in terminal-only shells in environments without
325 # continuing (such as in terminal-only shells in environments without
314 # zeromq available).
326 # zeromq available).
315 try:
327 try:
316 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
328 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
317 except ImportError:
329 except ImportError:
318 return
330 return
319 from matplotlib import pyplot
331 from matplotlib import pyplot
320
332
321 cfg = InlineBackend.instance(parent=shell)
333 cfg = InlineBackend.instance(parent=shell)
322 cfg.shell = shell
334 cfg.shell = shell
323 if cfg not in shell.configurables:
335 if cfg not in shell.configurables:
324 shell.configurables.append(cfg)
336 shell.configurables.append(cfg)
325
337
326 if backend == backends['inline']:
338 if backend == backends['inline']:
327 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
339 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
328 shell.register_post_execute(flush_figures)
340 shell.register_post_execute(flush_figures)
329
341
330 # Save rcParams that will be overwrittern
342 # Save rcParams that will be overwrittern
331 shell._saved_rcParams = dict()
343 shell._saved_rcParams = dict()
332 for k in cfg.rc:
344 for k in cfg.rc:
333 shell._saved_rcParams[k] = pyplot.rcParams[k]
345 shell._saved_rcParams[k] = pyplot.rcParams[k]
334 # load inline_rc
346 # load inline_rc
335 pyplot.rcParams.update(cfg.rc)
347 pyplot.rcParams.update(cfg.rc)
336 else:
348 else:
337 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
349 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
338 if flush_figures in shell._post_execute:
350 if flush_figures in shell._post_execute:
339 shell._post_execute.pop(flush_figures)
351 shell._post_execute.pop(flush_figures)
340 if hasattr(shell, '_saved_rcParams'):
352 if hasattr(shell, '_saved_rcParams'):
341 pyplot.rcParams.update(shell._saved_rcParams)
353 pyplot.rcParams.update(shell._saved_rcParams)
342 del shell._saved_rcParams
354 del shell._saved_rcParams
343
355
344 # Setup the default figure format
356 # Setup the default figure format
345 select_figure_format(shell, cfg.figure_format, cfg.quality)
357 select_figure_formats(shell, cfg.figure_formats, cfg.quality)
346
358
@@ -1,282 +1,291 b''
1 """Tests for the Formatters."""
1 """Tests for the Formatters."""
2
2
3 from math import pi
3 from math import pi
4
4
5 try:
5 try:
6 import numpy
6 import numpy
7 except:
7 except:
8 numpy = None
8 numpy = None
9 import nose.tools as nt
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 from IPython.utils.io import capture_output
14 from IPython.utils.io import capture_output
13
15
14 class A(object):
16 class A(object):
15 def __repr__(self):
17 def __repr__(self):
16 return 'A()'
18 return 'A()'
17
19
18 class B(A):
20 class B(A):
19 def __repr__(self):
21 def __repr__(self):
20 return 'B()'
22 return 'B()'
21
23
22 class C:
24 class C:
23 pass
25 pass
24
26
25 class BadPretty(object):
27 class BadPretty(object):
26 _repr_pretty_ = None
28 _repr_pretty_ = None
27
29
28 class GoodPretty(object):
30 class GoodPretty(object):
29 def _repr_pretty_(self, pp, cycle):
31 def _repr_pretty_(self, pp, cycle):
30 pp.text('foo')
32 pp.text('foo')
31
33
32 def __repr__(self):
34 def __repr__(self):
33 return 'GoodPretty()'
35 return 'GoodPretty()'
34
36
35 def foo_printer(obj, pp, cycle):
37 def foo_printer(obj, pp, cycle):
36 pp.text('foo')
38 pp.text('foo')
37
39
38 def test_pretty():
40 def test_pretty():
39 f = PlainTextFormatter()
41 f = PlainTextFormatter()
40 f.for_type(A, foo_printer)
42 f.for_type(A, foo_printer)
41 nt.assert_equal(f(A()), 'foo')
43 nt.assert_equal(f(A()), 'foo')
42 nt.assert_equal(f(B()), 'foo')
44 nt.assert_equal(f(B()), 'foo')
43 nt.assert_equal(f(GoodPretty()), 'foo')
45 nt.assert_equal(f(GoodPretty()), 'foo')
44 # Just don't raise an exception for the following:
46 # Just don't raise an exception for the following:
45 f(BadPretty())
47 f(BadPretty())
46
48
47 f.pprint = False
49 f.pprint = False
48 nt.assert_equal(f(A()), 'A()')
50 nt.assert_equal(f(A()), 'A()')
49 nt.assert_equal(f(B()), 'B()')
51 nt.assert_equal(f(B()), 'B()')
50 nt.assert_equal(f(GoodPretty()), 'GoodPretty()')
52 nt.assert_equal(f(GoodPretty()), 'GoodPretty()')
51
53
52
54
53 def test_deferred():
55 def test_deferred():
54 f = PlainTextFormatter()
56 f = PlainTextFormatter()
55
57
56 def test_precision():
58 def test_precision():
57 """test various values for float_precision."""
59 """test various values for float_precision."""
58 f = PlainTextFormatter()
60 f = PlainTextFormatter()
59 nt.assert_equal(f(pi), repr(pi))
61 nt.assert_equal(f(pi), repr(pi))
60 f.float_precision = 0
62 f.float_precision = 0
61 if numpy:
63 if numpy:
62 po = numpy.get_printoptions()
64 po = numpy.get_printoptions()
63 nt.assert_equal(po['precision'], 0)
65 nt.assert_equal(po['precision'], 0)
64 nt.assert_equal(f(pi), '3')
66 nt.assert_equal(f(pi), '3')
65 f.float_precision = 2
67 f.float_precision = 2
66 if numpy:
68 if numpy:
67 po = numpy.get_printoptions()
69 po = numpy.get_printoptions()
68 nt.assert_equal(po['precision'], 2)
70 nt.assert_equal(po['precision'], 2)
69 nt.assert_equal(f(pi), '3.14')
71 nt.assert_equal(f(pi), '3.14')
70 f.float_precision = '%g'
72 f.float_precision = '%g'
71 if numpy:
73 if numpy:
72 po = numpy.get_printoptions()
74 po = numpy.get_printoptions()
73 nt.assert_equal(po['precision'], 2)
75 nt.assert_equal(po['precision'], 2)
74 nt.assert_equal(f(pi), '3.14159')
76 nt.assert_equal(f(pi), '3.14159')
75 f.float_precision = '%e'
77 f.float_precision = '%e'
76 nt.assert_equal(f(pi), '3.141593e+00')
78 nt.assert_equal(f(pi), '3.141593e+00')
77 f.float_precision = ''
79 f.float_precision = ''
78 if numpy:
80 if numpy:
79 po = numpy.get_printoptions()
81 po = numpy.get_printoptions()
80 nt.assert_equal(po['precision'], 8)
82 nt.assert_equal(po['precision'], 8)
81 nt.assert_equal(f(pi), repr(pi))
83 nt.assert_equal(f(pi), repr(pi))
82
84
83 def test_bad_precision():
85 def test_bad_precision():
84 """test various invalid values for float_precision."""
86 """test various invalid values for float_precision."""
85 f = PlainTextFormatter()
87 f = PlainTextFormatter()
86 def set_fp(p):
88 def set_fp(p):
87 f.float_precision=p
89 f.float_precision=p
88 nt.assert_raises(ValueError, set_fp, '%')
90 nt.assert_raises(ValueError, set_fp, '%')
89 nt.assert_raises(ValueError, set_fp, '%.3f%i')
91 nt.assert_raises(ValueError, set_fp, '%.3f%i')
90 nt.assert_raises(ValueError, set_fp, 'foo')
92 nt.assert_raises(ValueError, set_fp, 'foo')
91 nt.assert_raises(ValueError, set_fp, -1)
93 nt.assert_raises(ValueError, set_fp, -1)
92
94
93 def test_for_type():
95 def test_for_type():
94 f = PlainTextFormatter()
96 f = PlainTextFormatter()
95
97
96 # initial return, None
98 # initial return, None
97 nt.assert_is(f.for_type(C, foo_printer), None)
99 nt.assert_is(f.for_type(C, foo_printer), None)
98 # no func queries
100 # no func queries
99 nt.assert_is(f.for_type(C), foo_printer)
101 nt.assert_is(f.for_type(C), foo_printer)
100 # shouldn't change anything
102 # shouldn't change anything
101 nt.assert_is(f.for_type(C), foo_printer)
103 nt.assert_is(f.for_type(C), foo_printer)
102 # None should do the same
104 # None should do the same
103 nt.assert_is(f.for_type(C, None), foo_printer)
105 nt.assert_is(f.for_type(C, None), foo_printer)
104 nt.assert_is(f.for_type(C, None), foo_printer)
106 nt.assert_is(f.for_type(C, None), foo_printer)
105
107
106 def test_for_type_string():
108 def test_for_type_string():
107 f = PlainTextFormatter()
109 f = PlainTextFormatter()
108
110
109 mod = C.__module__
111 mod = C.__module__
110
112
111 type_str = '%s.%s' % (C.__module__, 'C')
113 type_str = '%s.%s' % (C.__module__, 'C')
112
114
113 # initial return, None
115 # initial return, None
114 nt.assert_is(f.for_type(type_str, foo_printer), None)
116 nt.assert_is(f.for_type(type_str, foo_printer), None)
115 # no func queries
117 # no func queries
116 nt.assert_is(f.for_type(type_str), foo_printer)
118 nt.assert_is(f.for_type(type_str), foo_printer)
117 nt.assert_in(_mod_name_key(C), f.deferred_printers)
119 nt.assert_in(_mod_name_key(C), f.deferred_printers)
118 nt.assert_is(f.for_type(C), foo_printer)
120 nt.assert_is(f.for_type(C), foo_printer)
119 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
121 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
120 nt.assert_in(C, f.type_printers)
122 nt.assert_in(C, f.type_printers)
121
123
122 def test_for_type_by_name():
124 def test_for_type_by_name():
123 f = PlainTextFormatter()
125 f = PlainTextFormatter()
124
126
125 mod = C.__module__
127 mod = C.__module__
126
128
127 # initial return, None
129 # initial return, None
128 nt.assert_is(f.for_type_by_name(mod, 'C', foo_printer), None)
130 nt.assert_is(f.for_type_by_name(mod, 'C', foo_printer), None)
129 # no func queries
131 # no func queries
130 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
132 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
131 # shouldn't change anything
133 # shouldn't change anything
132 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
134 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
133 # None should do the same
135 # None should do the same
134 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
136 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
135 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
137 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
136
138
137 def test_lookup():
139 def test_lookup():
138 f = PlainTextFormatter()
140 f = PlainTextFormatter()
139
141
140 f.for_type(C, foo_printer)
142 f.for_type(C, foo_printer)
141 nt.assert_is(f.lookup(C()), foo_printer)
143 nt.assert_is(f.lookup(C()), foo_printer)
142 with nt.assert_raises(KeyError):
144 with nt.assert_raises(KeyError):
143 f.lookup(A())
145 f.lookup(A())
144
146
145 def test_lookup_string():
147 def test_lookup_string():
146 f = PlainTextFormatter()
148 f = PlainTextFormatter()
147 type_str = '%s.%s' % (C.__module__, 'C')
149 type_str = '%s.%s' % (C.__module__, 'C')
148
150
149 f.for_type(type_str, foo_printer)
151 f.for_type(type_str, foo_printer)
150 nt.assert_is(f.lookup(C()), foo_printer)
152 nt.assert_is(f.lookup(C()), foo_printer)
151 # should move from deferred to imported dict
153 # should move from deferred to imported dict
152 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
154 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
153 nt.assert_in(C, f.type_printers)
155 nt.assert_in(C, f.type_printers)
154
156
155 def test_lookup_by_type():
157 def test_lookup_by_type():
156 f = PlainTextFormatter()
158 f = PlainTextFormatter()
157 f.for_type(C, foo_printer)
159 f.for_type(C, foo_printer)
158 nt.assert_is(f.lookup_by_type(C), foo_printer)
160 nt.assert_is(f.lookup_by_type(C), foo_printer)
159 type_str = '%s.%s' % (C.__module__, 'C')
161 type_str = '%s.%s' % (C.__module__, 'C')
160 with nt.assert_raises(KeyError):
162 with nt.assert_raises(KeyError):
161 f.lookup_by_type(A)
163 f.lookup_by_type(A)
162
164
163 def test_lookup_by_type_string():
165 def test_lookup_by_type_string():
164 f = PlainTextFormatter()
166 f = PlainTextFormatter()
165 type_str = '%s.%s' % (C.__module__, 'C')
167 type_str = '%s.%s' % (C.__module__, 'C')
166 f.for_type(type_str, foo_printer)
168 f.for_type(type_str, foo_printer)
167
169
168 # verify insertion
170 # verify insertion
169 nt.assert_in(_mod_name_key(C), f.deferred_printers)
171 nt.assert_in(_mod_name_key(C), f.deferred_printers)
170 nt.assert_not_in(C, f.type_printers)
172 nt.assert_not_in(C, f.type_printers)
171
173
172 nt.assert_is(f.lookup_by_type(type_str), foo_printer)
174 nt.assert_is(f.lookup_by_type(type_str), foo_printer)
173 # lookup by string doesn't cause import
175 # lookup by string doesn't cause import
174 nt.assert_in(_mod_name_key(C), f.deferred_printers)
176 nt.assert_in(_mod_name_key(C), f.deferred_printers)
175 nt.assert_not_in(C, f.type_printers)
177 nt.assert_not_in(C, f.type_printers)
176
178
177 nt.assert_is(f.lookup_by_type(C), foo_printer)
179 nt.assert_is(f.lookup_by_type(C), foo_printer)
178 # should move from deferred to imported dict
180 # should move from deferred to imported dict
179 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
181 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
180 nt.assert_in(C, f.type_printers)
182 nt.assert_in(C, f.type_printers)
181
183
182 def test_in_formatter():
184 def test_in_formatter():
183 f = PlainTextFormatter()
185 f = PlainTextFormatter()
184 f.for_type(C, foo_printer)
186 f.for_type(C, foo_printer)
185 type_str = '%s.%s' % (C.__module__, 'C')
187 type_str = '%s.%s' % (C.__module__, 'C')
186 nt.assert_in(C, f)
188 nt.assert_in(C, f)
187 nt.assert_in(type_str, f)
189 nt.assert_in(type_str, f)
188
190
189 def test_string_in_formatter():
191 def test_string_in_formatter():
190 f = PlainTextFormatter()
192 f = PlainTextFormatter()
191 type_str = '%s.%s' % (C.__module__, 'C')
193 type_str = '%s.%s' % (C.__module__, 'C')
192 f.for_type(type_str, foo_printer)
194 f.for_type(type_str, foo_printer)
193 nt.assert_in(type_str, f)
195 nt.assert_in(type_str, f)
194 nt.assert_in(C, f)
196 nt.assert_in(C, f)
195
197
196 def test_pop():
198 def test_pop():
197 f = PlainTextFormatter()
199 f = PlainTextFormatter()
198 f.for_type(C, foo_printer)
200 f.for_type(C, foo_printer)
199 nt.assert_is(f.lookup_by_type(C), foo_printer)
201 nt.assert_is(f.lookup_by_type(C), foo_printer)
200 nt.assert_is(f.pop(C, None), foo_printer)
202 nt.assert_is(f.pop(C, None), foo_printer)
201 f.for_type(C, foo_printer)
203 f.for_type(C, foo_printer)
202 nt.assert_is(f.pop(C), foo_printer)
204 nt.assert_is(f.pop(C), foo_printer)
203 with nt.assert_raises(KeyError):
205 with nt.assert_raises(KeyError):
204 f.lookup_by_type(C)
206 f.lookup_by_type(C)
205 with nt.assert_raises(KeyError):
207 with nt.assert_raises(KeyError):
206 f.pop(C)
208 f.pop(C)
207 with nt.assert_raises(KeyError):
209 with nt.assert_raises(KeyError):
208 f.pop(A)
210 f.pop(A)
209 nt.assert_is(f.pop(A, None), None)
211 nt.assert_is(f.pop(A, None), None)
210
212
211 def test_pop_string():
213 def test_pop_string():
212 f = PlainTextFormatter()
214 f = PlainTextFormatter()
213 type_str = '%s.%s' % (C.__module__, 'C')
215 type_str = '%s.%s' % (C.__module__, 'C')
214
216
215 with nt.assert_raises(KeyError):
217 with nt.assert_raises(KeyError):
216 f.pop(type_str)
218 f.pop(type_str)
217
219
218 f.for_type(type_str, foo_printer)
220 f.for_type(type_str, foo_printer)
219 f.pop(type_str)
221 f.pop(type_str)
220 with nt.assert_raises(KeyError):
222 with nt.assert_raises(KeyError):
221 f.lookup_by_type(C)
223 f.lookup_by_type(C)
222 with nt.assert_raises(KeyError):
224 with nt.assert_raises(KeyError):
223 f.pop(type_str)
225 f.pop(type_str)
224
226
225 f.for_type(C, foo_printer)
227 f.for_type(C, foo_printer)
226 nt.assert_is(f.pop(type_str, None), foo_printer)
228 nt.assert_is(f.pop(type_str, None), foo_printer)
227 with nt.assert_raises(KeyError):
229 with nt.assert_raises(KeyError):
228 f.lookup_by_type(C)
230 f.lookup_by_type(C)
229 with nt.assert_raises(KeyError):
231 with nt.assert_raises(KeyError):
230 f.pop(type_str)
232 f.pop(type_str)
231 nt.assert_is(f.pop(type_str, None), None)
233 nt.assert_is(f.pop(type_str, None), None)
232
234
233
235
234 def test_warn_error_method():
236 def test_warn_error_method():
235 f = HTMLFormatter()
237 f = HTMLFormatter()
236 class BadHTML(object):
238 class BadHTML(object):
237 def _repr_html_(self):
239 def _repr_html_(self):
238 return 1/0
240 return 1/0
239 bad = BadHTML()
241 bad = BadHTML()
240 with capture_output() as captured:
242 with capture_output() as captured:
241 result = f(bad)
243 result = f(bad)
242 nt.assert_is(result, None)
244 nt.assert_is(result, None)
243 nt.assert_in("FormatterWarning", captured.stderr)
245 nt.assert_in("FormatterWarning", captured.stderr)
244 nt.assert_in("text/html", captured.stderr)
246 nt.assert_in("text/html", captured.stderr)
245 nt.assert_in("zero", captured.stderr)
247 nt.assert_in("zero", captured.stderr)
246
248
247 def test_nowarn_notimplemented():
249 def test_nowarn_notimplemented():
248 f = HTMLFormatter()
250 f = HTMLFormatter()
249 class HTMLNotImplemented(object):
251 class HTMLNotImplemented(object):
250 def _repr_html_(self):
252 def _repr_html_(self):
251 raise NotImplementedError
253 raise NotImplementedError
252 return 1/0
254 return 1/0
253 h = HTMLNotImplemented()
255 h = HTMLNotImplemented()
254 with capture_output() as captured:
256 with capture_output() as captured:
255 result = f(h)
257 result = f(h)
256 nt.assert_is(result, None)
258 nt.assert_is(result, None)
257 nt.assert_not_in("FormatterWarning", captured.stderr)
259 nt.assert_not_in("FormatterWarning", captured.stderr)
258
260
259 def test_warn_error_for_type():
261 def test_warn_error_for_type():
260 f = HTMLFormatter()
262 f = HTMLFormatter()
261 f.for_type(int, lambda i: name_error)
263 f.for_type(int, lambda i: name_error)
262 with capture_output() as captured:
264 with capture_output() as captured:
263 result = f(5)
265 result = f(5)
264 nt.assert_is(result, None)
266 nt.assert_is(result, None)
265 nt.assert_in("FormatterWarning", captured.stderr)
267 nt.assert_in("FormatterWarning", captured.stderr)
266 nt.assert_in("text/html", captured.stderr)
268 nt.assert_in("text/html", captured.stderr)
267 nt.assert_in("name_error", captured.stderr)
269 nt.assert_in("name_error", captured.stderr)
268
270
269 def test_warn_error_pretty_method():
271 def test_warn_error_pretty_method():
270 f = PlainTextFormatter()
272 f = PlainTextFormatter()
271 class BadPretty(object):
273 class BadPretty(object):
272 def _repr_pretty_(self):
274 def _repr_pretty_(self):
273 return "hello"
275 return "hello"
274 bad = BadPretty()
276 bad = BadPretty()
275 with capture_output() as captured:
277 with capture_output() as captured:
276 result = f(bad)
278 result = f(bad)
277 nt.assert_is(result, None)
279 nt.assert_is(result, None)
278 nt.assert_in("FormatterWarning", captured.stderr)
280 nt.assert_in("FormatterWarning", captured.stderr)
279 nt.assert_in("text/plain", captured.stderr)
281 nt.assert_in("text/plain", captured.stderr)
280 nt.assert_in("argument", captured.stderr)
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,851 +1,867 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.trusted = true;
34 this.trusted = true;
35 this.clear_queued = null;
35 this.clear_queued = null;
36 if (prompt_area === undefined) {
36 if (prompt_area === undefined) {
37 this.prompt_area = true;
37 this.prompt_area = true;
38 } else {
38 } else {
39 this.prompt_area = prompt_area;
39 this.prompt_area = prompt_area;
40 }
40 }
41 this.create_elements();
41 this.create_elements();
42 this.style();
42 this.style();
43 this.bind_events();
43 this.bind_events();
44 };
44 };
45
45
46
46
47 /**
47 /**
48 * Class prototypes
48 * Class prototypes
49 **/
49 **/
50
50
51 OutputArea.prototype.create_elements = function () {
51 OutputArea.prototype.create_elements = function () {
52 this.element = $("<div/>");
52 this.element = $("<div/>");
53 this.collapse_button = $("<div/>");
53 this.collapse_button = $("<div/>");
54 this.prompt_overlay = $("<div/>");
54 this.prompt_overlay = $("<div/>");
55 this.wrapper.append(this.prompt_overlay);
55 this.wrapper.append(this.prompt_overlay);
56 this.wrapper.append(this.element);
56 this.wrapper.append(this.element);
57 this.wrapper.append(this.collapse_button);
57 this.wrapper.append(this.collapse_button);
58 };
58 };
59
59
60
60
61 OutputArea.prototype.style = function () {
61 OutputArea.prototype.style = function () {
62 this.collapse_button.hide();
62 this.collapse_button.hide();
63 this.prompt_overlay.hide();
63 this.prompt_overlay.hide();
64
64
65 this.wrapper.addClass('output_wrapper');
65 this.wrapper.addClass('output_wrapper');
66 this.element.addClass('output');
66 this.element.addClass('output');
67
67
68 this.collapse_button.addClass("btn output_collapsed");
68 this.collapse_button.addClass("btn output_collapsed");
69 this.collapse_button.attr('title', 'click to expand output');
69 this.collapse_button.attr('title', 'click to expand output');
70 this.collapse_button.text('. . .');
70 this.collapse_button.text('. . .');
71
71
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74
74
75 this.collapse();
75 this.collapse();
76 };
76 };
77
77
78 /**
78 /**
79 * Should the OutputArea scroll?
79 * Should the OutputArea scroll?
80 * Returns whether the height (in lines) exceeds a threshold.
80 * Returns whether the height (in lines) exceeds a threshold.
81 *
81 *
82 * @private
82 * @private
83 * @method _should_scroll
83 * @method _should_scroll
84 * @param [lines=100]{Integer}
84 * @param [lines=100]{Integer}
85 * @return {Bool}
85 * @return {Bool}
86 *
86 *
87 */
87 */
88 OutputArea.prototype._should_scroll = function (lines) {
88 OutputArea.prototype._should_scroll = function (lines) {
89 if (lines <=0 ){ return }
89 if (lines <=0 ){ return }
90 if (!lines) {
90 if (!lines) {
91 lines = 100;
91 lines = 100;
92 }
92 }
93 // line-height from http://stackoverflow.com/questions/1185151
93 // line-height from http://stackoverflow.com/questions/1185151
94 var fontSize = this.element.css('font-size');
94 var fontSize = this.element.css('font-size');
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96
96
97 return (this.element.height() > lines * lineHeight);
97 return (this.element.height() > lines * lineHeight);
98 };
98 };
99
99
100
100
101 OutputArea.prototype.bind_events = function () {
101 OutputArea.prototype.bind_events = function () {
102 var that = this;
102 var that = this;
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105
105
106 this.element.resize(function () {
106 this.element.resize(function () {
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 if ( IPython.utils.browser[0] === "Firefox" ) {
108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 return;
109 return;
110 }
110 }
111 // maybe scroll output,
111 // maybe scroll output,
112 // if it's grown large enough and hasn't already been scrolled.
112 // if it's grown large enough and hasn't already been scrolled.
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 that.scroll_area();
114 that.scroll_area();
115 }
115 }
116 });
116 });
117 this.collapse_button.click(function () {
117 this.collapse_button.click(function () {
118 that.expand();
118 that.expand();
119 });
119 });
120 };
120 };
121
121
122
122
123 OutputArea.prototype.collapse = function () {
123 OutputArea.prototype.collapse = function () {
124 if (!this.collapsed) {
124 if (!this.collapsed) {
125 this.element.hide();
125 this.element.hide();
126 this.prompt_overlay.hide();
126 this.prompt_overlay.hide();
127 if (this.element.html()){
127 if (this.element.html()){
128 this.collapse_button.show();
128 this.collapse_button.show();
129 }
129 }
130 this.collapsed = true;
130 this.collapsed = true;
131 }
131 }
132 };
132 };
133
133
134
134
135 OutputArea.prototype.expand = function () {
135 OutputArea.prototype.expand = function () {
136 if (this.collapsed) {
136 if (this.collapsed) {
137 this.collapse_button.hide();
137 this.collapse_button.hide();
138 this.element.show();
138 this.element.show();
139 this.prompt_overlay.show();
139 this.prompt_overlay.show();
140 this.collapsed = false;
140 this.collapsed = false;
141 }
141 }
142 };
142 };
143
143
144
144
145 OutputArea.prototype.toggle_output = function () {
145 OutputArea.prototype.toggle_output = function () {
146 if (this.collapsed) {
146 if (this.collapsed) {
147 this.expand();
147 this.expand();
148 } else {
148 } else {
149 this.collapse();
149 this.collapse();
150 }
150 }
151 };
151 };
152
152
153
153
154 OutputArea.prototype.scroll_area = function () {
154 OutputArea.prototype.scroll_area = function () {
155 this.element.addClass('output_scroll');
155 this.element.addClass('output_scroll');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 this.scrolled = true;
157 this.scrolled = true;
158 };
158 };
159
159
160
160
161 OutputArea.prototype.unscroll_area = function () {
161 OutputArea.prototype.unscroll_area = function () {
162 this.element.removeClass('output_scroll');
162 this.element.removeClass('output_scroll');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 this.scrolled = false;
164 this.scrolled = false;
165 };
165 };
166
166
167 /**
167 /**
168 *
168 *
169 * Scroll OutputArea if height supperior than a threshold (in lines).
169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 *
170 *
171 * Threshold is a maximum number of lines. If unspecified, defaults to
171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 * OutputArea.minimum_scroll_threshold.
172 * OutputArea.minimum_scroll_threshold.
173 *
173 *
174 * Negative threshold will prevent the OutputArea from ever scrolling.
174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 *
175 *
176 * @method scroll_if_long
176 * @method scroll_if_long
177 *
177 *
178 * @param [lines=20]{Number} Default to 20 if not set,
178 * @param [lines=20]{Number} Default to 20 if not set,
179 * behavior undefined for value of `0`.
179 * behavior undefined for value of `0`.
180 *
180 *
181 **/
181 **/
182 OutputArea.prototype.scroll_if_long = function (lines) {
182 OutputArea.prototype.scroll_if_long = function (lines) {
183 var n = lines | OutputArea.minimum_scroll_threshold;
183 var n = lines | OutputArea.minimum_scroll_threshold;
184 if(n <= 0){
184 if(n <= 0){
185 return
185 return
186 }
186 }
187
187
188 if (this._should_scroll(n)) {
188 if (this._should_scroll(n)) {
189 // only allow scrolling long-enough output
189 // only allow scrolling long-enough output
190 this.scroll_area();
190 this.scroll_area();
191 }
191 }
192 };
192 };
193
193
194
194
195 OutputArea.prototype.toggle_scroll = function () {
195 OutputArea.prototype.toggle_scroll = function () {
196 if (this.scrolled) {
196 if (this.scrolled) {
197 this.unscroll_area();
197 this.unscroll_area();
198 } else {
198 } else {
199 // only allow scrolling long-enough output
199 // only allow scrolling long-enough output
200 this.scroll_if_long();
200 this.scroll_if_long();
201 }
201 }
202 };
202 };
203
203
204
204
205 // typeset with MathJax if MathJax is available
205 // typeset with MathJax if MathJax is available
206 OutputArea.prototype.typeset = function () {
206 OutputArea.prototype.typeset = function () {
207 if (window.MathJax){
207 if (window.MathJax){
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 }
209 }
210 };
210 };
211
211
212
212
213 OutputArea.prototype.handle_output = function (msg) {
213 OutputArea.prototype.handle_output = function (msg) {
214 var json = {};
214 var json = {};
215 var msg_type = json.output_type = msg.header.msg_type;
215 var msg_type = json.output_type = msg.header.msg_type;
216 var content = msg.content;
216 var content = msg.content;
217 if (msg_type === "stream") {
217 if (msg_type === "stream") {
218 json.text = content.data;
218 json.text = content.data;
219 json.stream = content.name;
219 json.stream = content.name;
220 } else if (msg_type === "display_data") {
220 } else if (msg_type === "display_data") {
221 json = content.data;
221 json = content.data;
222 json.output_type = msg_type;
222 json.output_type = msg_type;
223 json.metadata = content.metadata;
223 json.metadata = content.metadata;
224 } else if (msg_type === "pyout") {
224 } else if (msg_type === "pyout") {
225 json = content.data;
225 json = content.data;
226 json.output_type = msg_type;
226 json.output_type = msg_type;
227 json.metadata = content.metadata;
227 json.metadata = content.metadata;
228 json.prompt_number = content.execution_count;
228 json.prompt_number = content.execution_count;
229 } else if (msg_type === "pyerr") {
229 } else if (msg_type === "pyerr") {
230 json.ename = content.ename;
230 json.ename = content.ename;
231 json.evalue = content.evalue;
231 json.evalue = content.evalue;
232 json.traceback = content.traceback;
232 json.traceback = content.traceback;
233 }
233 }
234 this.append_output(json);
234 this.append_output(json);
235 };
235 };
236
236
237
237
238 OutputArea.prototype.rename_keys = function (data, key_map) {
238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 var remapped = {};
239 var remapped = {};
240 for (var key in data) {
240 for (var key in data) {
241 var new_key = key_map[key] || key;
241 var new_key = key_map[key] || key;
242 remapped[new_key] = data[key];
242 remapped[new_key] = data[key];
243 }
243 }
244 return remapped;
244 return remapped;
245 };
245 };
246
246
247
247
248 OutputArea.output_types = [
248 OutputArea.output_types = [
249 'application/javascript',
249 'application/javascript',
250 'text/html',
250 'text/html',
251 'text/latex',
251 'text/latex',
252 'image/svg+xml',
252 'image/svg+xml',
253 'image/png',
253 'image/png',
254 'image/jpeg',
254 'image/jpeg',
255 'application/pdf',
255 'text/plain'
256 'text/plain'
256 ];
257 ];
257
258
258 OutputArea.prototype.validate_output = function (json) {
259 OutputArea.prototype.validate_output = function (json) {
259 // scrub invalid outputs
260 // scrub invalid outputs
260 // TODO: right now everything is a string, but JSON really shouldn't be.
261 // TODO: right now everything is a string, but JSON really shouldn't be.
261 // nbformat 4 will fix that.
262 // nbformat 4 will fix that.
262 $.map(OutputArea.output_types, function(key){
263 $.map(OutputArea.output_types, function(key){
263 if (json[key] !== undefined && typeof json[key] !== 'string') {
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
264 console.log("Invalid type for " + key, json[key]);
265 console.log("Invalid type for " + key, json[key]);
265 delete json[key];
266 delete json[key];
266 }
267 }
267 });
268 });
268 return json;
269 return json;
269 };
270 };
270
271
271 OutputArea.prototype.append_output = function (json) {
272 OutputArea.prototype.append_output = function (json) {
272 this.expand();
273 this.expand();
273 // Clear the output if clear is queued.
274 // Clear the output if clear is queued.
274 var needs_height_reset = false;
275 var needs_height_reset = false;
275 if (this.clear_queued) {
276 if (this.clear_queued) {
276 this.clear_output(false);
277 this.clear_output(false);
277 needs_height_reset = true;
278 needs_height_reset = true;
278 }
279 }
279
280
280 // validate output data types
281 // validate output data types
281 json = this.validate_output(json);
282 json = this.validate_output(json);
282
283
283 if (json.output_type === 'pyout') {
284 if (json.output_type === 'pyout') {
284 this.append_pyout(json);
285 this.append_pyout(json);
285 } else if (json.output_type === 'pyerr') {
286 } else if (json.output_type === 'pyerr') {
286 this.append_pyerr(json);
287 this.append_pyerr(json);
287 } else if (json.output_type === 'display_data') {
288 } else if (json.output_type === 'display_data') {
288 this.append_display_data(json);
289 this.append_display_data(json);
289 } else if (json.output_type === 'stream') {
290 } else if (json.output_type === 'stream') {
290 this.append_stream(json);
291 this.append_stream(json);
291 }
292 }
292
293
293 this.outputs.push(json);
294 this.outputs.push(json);
294
295
295 // Only reset the height to automatic if the height is currently
296 // Only reset the height to automatic if the height is currently
296 // fixed (done by wait=True flag on clear_output).
297 // fixed (done by wait=True flag on clear_output).
297 if (needs_height_reset) {
298 if (needs_height_reset) {
298 this.element.height('');
299 this.element.height('');
299 }
300 }
300
301
301 var that = this;
302 var that = this;
302 setTimeout(function(){that.element.trigger('resize');}, 100);
303 setTimeout(function(){that.element.trigger('resize');}, 100);
303 };
304 };
304
305
305
306
306 OutputArea.prototype.create_output_area = function () {
307 OutputArea.prototype.create_output_area = function () {
307 var oa = $("<div/>").addClass("output_area");
308 var oa = $("<div/>").addClass("output_area");
308 if (this.prompt_area) {
309 if (this.prompt_area) {
309 oa.append($('<div/>').addClass('prompt'));
310 oa.append($('<div/>').addClass('prompt'));
310 }
311 }
311 return oa;
312 return oa;
312 };
313 };
313
314
314
315
315 function _get_metadata_key(metadata, key, mime) {
316 function _get_metadata_key(metadata, key, mime) {
316 var mime_md = metadata[mime];
317 var mime_md = metadata[mime];
317 // mime-specific higher priority
318 // mime-specific higher priority
318 if (mime_md && mime_md[key] !== undefined) {
319 if (mime_md && mime_md[key] !== undefined) {
319 return mime_md[key];
320 return mime_md[key];
320 }
321 }
321 // fallback on global
322 // fallback on global
322 return metadata[key];
323 return metadata[key];
323 }
324 }
324
325
325 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
326 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
327 if (_get_metadata_key(md, 'isolated', mime)) {
328 if (_get_metadata_key(md, 'isolated', mime)) {
328 // Create an iframe to isolate the subarea from the rest of the
329 // Create an iframe to isolate the subarea from the rest of the
329 // document
330 // document
330 var iframe = $('<iframe/>').addClass('box-flex1');
331 var iframe = $('<iframe/>').addClass('box-flex1');
331 iframe.css({'height':1, 'width':'100%', 'display':'block'});
332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
332 iframe.attr('frameborder', 0);
333 iframe.attr('frameborder', 0);
333 iframe.attr('scrolling', 'auto');
334 iframe.attr('scrolling', 'auto');
334
335
335 // Once the iframe is loaded, the subarea is dynamically inserted
336 // Once the iframe is loaded, the subarea is dynamically inserted
336 iframe.on('load', function() {
337 iframe.on('load', function() {
337 // Workaround needed by Firefox, to properly render svg inside
338 // Workaround needed by Firefox, to properly render svg inside
338 // iframes, see http://stackoverflow.com/questions/10177190/
339 // iframes, see http://stackoverflow.com/questions/10177190/
339 // svg-dynamically-added-to-iframe-does-not-render-correctly
340 // svg-dynamically-added-to-iframe-does-not-render-correctly
340 this.contentDocument.open();
341 this.contentDocument.open();
341
342
342 // Insert the subarea into the iframe
343 // Insert the subarea into the iframe
343 // We must directly write the html. When using Jquery's append
344 // We must directly write the html. When using Jquery's append
344 // method, javascript is evaluated in the parent document and
345 // method, javascript is evaluated in the parent document and
345 // not in the iframe document.
346 // not in the iframe document.
346 this.contentDocument.write(subarea.html());
347 this.contentDocument.write(subarea.html());
347
348
348 this.contentDocument.close();
349 this.contentDocument.close();
349
350
350 var body = this.contentDocument.body;
351 var body = this.contentDocument.body;
351 // Adjust the iframe height automatically
352 // Adjust the iframe height automatically
352 iframe.height(body.scrollHeight + 'px');
353 iframe.height(body.scrollHeight + 'px');
353 });
354 });
354
355
355 // Elements should be appended to the inner subarea and not to the
356 // Elements should be appended to the inner subarea and not to the
356 // iframe
357 // iframe
357 iframe.append = function(that) {
358 iframe.append = function(that) {
358 subarea.append(that);
359 subarea.append(that);
359 };
360 };
360
361
361 return iframe;
362 return iframe;
362 } else {
363 } else {
363 return subarea;
364 return subarea;
364 }
365 }
365 }
366 }
366
367
367
368
368 OutputArea.prototype._append_javascript_error = function (err, element) {
369 OutputArea.prototype._append_javascript_error = function (err, element) {
369 // display a message when a javascript error occurs in display output
370 // display a message when a javascript error occurs in display output
370 var msg = "Javascript error adding output!"
371 var msg = "Javascript error adding output!"
371 if ( element === undefined ) return;
372 if ( element === undefined ) return;
372 element.append(
373 element.append(
373 $('<div/>').html(msg + "<br/>" +
374 $('<div/>').html(msg + "<br/>" +
374 err.toString() +
375 err.toString() +
375 '<br/>See your browser Javascript console for more details.'
376 '<br/>See your browser Javascript console for more details.'
376 ).addClass('js-error')
377 ).addClass('js-error')
377 );
378 );
378 };
379 };
379
380
380 OutputArea.prototype._safe_append = function (toinsert) {
381 OutputArea.prototype._safe_append = function (toinsert) {
381 // safely append an item to the document
382 // safely append an item to the document
382 // this is an object created by user code,
383 // this is an object created by user code,
383 // and may have errors, which should not be raised
384 // and may have errors, which should not be raised
384 // under any circumstances.
385 // under any circumstances.
385 try {
386 try {
386 this.element.append(toinsert);
387 this.element.append(toinsert);
387 } catch(err) {
388 } catch(err) {
388 console.log(err);
389 console.log(err);
389 // Create an actual output_area and output_subarea, which creates
390 // Create an actual output_area and output_subarea, which creates
390 // the prompt area and the proper indentation.
391 // the prompt area and the proper indentation.
391 var toinsert = this.create_output_area();
392 var toinsert = this.create_output_area();
392 var subarea = $('<div/>').addClass('output_subarea');
393 var subarea = $('<div/>').addClass('output_subarea');
393 toinsert.append(subarea);
394 toinsert.append(subarea);
394 this._append_javascript_error(err, subarea);
395 this._append_javascript_error(err, subarea);
395 this.element.append(toinsert);
396 this.element.append(toinsert);
396 }
397 }
397 };
398 };
398
399
399
400
400 OutputArea.prototype.append_pyout = function (json) {
401 OutputArea.prototype.append_pyout = function (json) {
401 var n = json.prompt_number || ' ';
402 var n = json.prompt_number || ' ';
402 var toinsert = this.create_output_area();
403 var toinsert = this.create_output_area();
403 if (this.prompt_area) {
404 if (this.prompt_area) {
404 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
405 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
405 }
406 }
406 this.append_mime_type(json, toinsert);
407 this.append_mime_type(json, toinsert);
407 this._safe_append(toinsert);
408 this._safe_append(toinsert);
408 // If we just output latex, typeset it.
409 // If we just output latex, typeset it.
409 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
410 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
410 this.typeset();
411 this.typeset();
411 }
412 }
412 };
413 };
413
414
414
415
415 OutputArea.prototype.append_pyerr = function (json) {
416 OutputArea.prototype.append_pyerr = function (json) {
416 var tb = json.traceback;
417 var tb = json.traceback;
417 if (tb !== undefined && tb.length > 0) {
418 if (tb !== undefined && tb.length > 0) {
418 var s = '';
419 var s = '';
419 var len = tb.length;
420 var len = tb.length;
420 for (var i=0; i<len; i++) {
421 for (var i=0; i<len; i++) {
421 s = s + tb[i] + '\n';
422 s = s + tb[i] + '\n';
422 }
423 }
423 s = s + '\n';
424 s = s + '\n';
424 var toinsert = this.create_output_area();
425 var toinsert = this.create_output_area();
425 this.append_text(s, {}, toinsert);
426 this.append_text(s, {}, toinsert);
426 this._safe_append(toinsert);
427 this._safe_append(toinsert);
427 }
428 }
428 };
429 };
429
430
430
431
431 OutputArea.prototype.append_stream = function (json) {
432 OutputArea.prototype.append_stream = function (json) {
432 // temporary fix: if stream undefined (json file written prior to this patch),
433 // temporary fix: if stream undefined (json file written prior to this patch),
433 // default to most likely stdout:
434 // default to most likely stdout:
434 if (json.stream == undefined){
435 if (json.stream == undefined){
435 json.stream = 'stdout';
436 json.stream = 'stdout';
436 }
437 }
437 var text = json.text;
438 var text = json.text;
438 var subclass = "output_"+json.stream;
439 var subclass = "output_"+json.stream;
439 if (this.outputs.length > 0){
440 if (this.outputs.length > 0){
440 // have at least one output to consider
441 // have at least one output to consider
441 var last = this.outputs[this.outputs.length-1];
442 var last = this.outputs[this.outputs.length-1];
442 if (last.output_type == 'stream' && json.stream == last.stream){
443 if (last.output_type == 'stream' && json.stream == last.stream){
443 // latest output was in the same stream,
444 // latest output was in the same stream,
444 // so append directly into its pre tag
445 // so append directly into its pre tag
445 // escape ANSI & HTML specials:
446 // escape ANSI & HTML specials:
446 var pre = this.element.find('div.'+subclass).last().find('pre');
447 var pre = this.element.find('div.'+subclass).last().find('pre');
447 var html = utils.fixCarriageReturn(
448 var html = utils.fixCarriageReturn(
448 pre.html() + utils.fixConsole(text));
449 pre.html() + utils.fixConsole(text));
449 pre.html(html);
450 pre.html(html);
450 return;
451 return;
451 }
452 }
452 }
453 }
453
454
454 if (!text.replace("\r", "")) {
455 if (!text.replace("\r", "")) {
455 // text is nothing (empty string, \r, etc.)
456 // text is nothing (empty string, \r, etc.)
456 // so don't append any elements, which might add undesirable space
457 // so don't append any elements, which might add undesirable space
457 return;
458 return;
458 }
459 }
459
460
460 // If we got here, attach a new div
461 // If we got here, attach a new div
461 var toinsert = this.create_output_area();
462 var toinsert = this.create_output_area();
462 this.append_text(text, {}, toinsert, "output_stream "+subclass);
463 this.append_text(text, {}, toinsert, "output_stream "+subclass);
463 this._safe_append(toinsert);
464 this._safe_append(toinsert);
464 };
465 };
465
466
466
467
467 OutputArea.prototype.append_display_data = function (json) {
468 OutputArea.prototype.append_display_data = function (json) {
468 var toinsert = this.create_output_area();
469 var toinsert = this.create_output_area();
469 if (this.append_mime_type(json, toinsert)) {
470 if (this.append_mime_type(json, toinsert)) {
470 this._safe_append(toinsert);
471 this._safe_append(toinsert);
471 // If we just output latex, typeset it.
472 // If we just output latex, typeset it.
472 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
473 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
473 this.typeset();
474 this.typeset();
474 }
475 }
475 }
476 }
476 };
477 };
477
478
478
479
479 OutputArea.safe_outputs = {
480 OutputArea.safe_outputs = {
480 'text/plain' : true,
481 'text/plain' : true,
481 'image/png' : true,
482 'image/png' : true,
482 'image/jpeg' : true
483 'image/jpeg' : true
483 };
484 };
484
485
485 OutputArea.prototype.append_mime_type = function (json, element) {
486 OutputArea.prototype.append_mime_type = function (json, element) {
486 for (var type_i in OutputArea.display_order) {
487 for (var type_i in OutputArea.display_order) {
487 var type = OutputArea.display_order[type_i];
488 var type = OutputArea.display_order[type_i];
488 var append = OutputArea.append_map[type];
489 var append = OutputArea.append_map[type];
489 if ((json[type] !== undefined) && append) {
490 if ((json[type] !== undefined) && append) {
490 if (!this.trusted && !OutputArea.safe_outputs[type]) {
491 if (!this.trusted && !OutputArea.safe_outputs[type]) {
491 // not trusted show warning and do not display
492 // not trusted show warning and do not display
492 var content = {
493 var content = {
493 text : "Untrusted " + type + " output ignored.",
494 text : "Untrusted " + type + " output ignored.",
494 stream : "stderr"
495 stream : "stderr"
495 }
496 }
496 this.append_stream(content);
497 this.append_stream(content);
497 continue;
498 continue;
498 }
499 }
499 var md = json.metadata || {};
500 var md = json.metadata || {};
500 var toinsert = append.apply(this, [json[type], md, element]);
501 var toinsert = append.apply(this, [json[type], md, element]);
501 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
502 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
502 return true;
503 return true;
503 }
504 }
504 }
505 }
505 return false;
506 return false;
506 };
507 };
507
508
508
509
509 OutputArea.prototype.append_html = function (html, md, element) {
510 OutputArea.prototype.append_html = function (html, md, element) {
510 var type = 'text/html';
511 var type = 'text/html';
511 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
512 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
512 IPython.keyboard_manager.register_events(toinsert);
513 IPython.keyboard_manager.register_events(toinsert);
513 toinsert.append(html);
514 toinsert.append(html);
514 element.append(toinsert);
515 element.append(toinsert);
515 return toinsert;
516 return toinsert;
516 };
517 };
517
518
518
519
519 OutputArea.prototype.append_javascript = function (js, md, element) {
520 OutputArea.prototype.append_javascript = function (js, md, element) {
520 // We just eval the JS code, element appears in the local scope.
521 // We just eval the JS code, element appears in the local scope.
521 var type = 'application/javascript';
522 var type = 'application/javascript';
522 var toinsert = this.create_output_subarea(md, "output_javascript", type);
523 var toinsert = this.create_output_subarea(md, "output_javascript", type);
523 IPython.keyboard_manager.register_events(toinsert);
524 IPython.keyboard_manager.register_events(toinsert);
524 element.append(toinsert);
525 element.append(toinsert);
525 // FIXME TODO : remove `container element for 3.0`
526 // FIXME TODO : remove `container element for 3.0`
526 //backward compat, js should be eval'ed in a context where `container` is defined.
527 //backward compat, js should be eval'ed in a context where `container` is defined.
527 var container = element;
528 var container = element;
528 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
529 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
529 // end backward compat
530 // end backward compat
530 try {
531 try {
531 eval(js);
532 eval(js);
532 } catch(err) {
533 } catch(err) {
533 console.log(err);
534 console.log(err);
534 this._append_javascript_error(err, toinsert);
535 this._append_javascript_error(err, toinsert);
535 }
536 }
536 return toinsert;
537 return toinsert;
537 };
538 };
538
539
539
540
540 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
541 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
541 var type = 'text/plain';
542 var type = 'text/plain';
542 var toinsert = this.create_output_subarea(md, "output_text", type);
543 var toinsert = this.create_output_subarea(md, "output_text", type);
543 // escape ANSI & HTML specials in plaintext:
544 // escape ANSI & HTML specials in plaintext:
544 data = utils.fixConsole(data);
545 data = utils.fixConsole(data);
545 data = utils.fixCarriageReturn(data);
546 data = utils.fixCarriageReturn(data);
546 data = utils.autoLinkUrls(data);
547 data = utils.autoLinkUrls(data);
547 if (extra_class){
548 if (extra_class){
548 toinsert.addClass(extra_class);
549 toinsert.addClass(extra_class);
549 }
550 }
550 toinsert.append($("<pre/>").html(data));
551 toinsert.append($("<pre/>").html(data));
551 element.append(toinsert);
552 element.append(toinsert);
552 return toinsert;
553 return toinsert;
553 };
554 };
554
555
555
556
556 OutputArea.prototype.append_svg = function (svg, md, element) {
557 OutputArea.prototype.append_svg = function (svg, md, element) {
557 var type = 'image/svg+xml';
558 var type = 'image/svg+xml';
558 var toinsert = this.create_output_subarea(md, "output_svg", type);
559 var toinsert = this.create_output_subarea(md, "output_svg", type);
559 toinsert.append(svg);
560 toinsert.append(svg);
560 element.append(toinsert);
561 element.append(toinsert);
561 return toinsert;
562 return toinsert;
562 };
563 };
563
564
564
565
565 OutputArea.prototype._dblclick_to_reset_size = function (img) {
566 OutputArea.prototype._dblclick_to_reset_size = function (img) {
566 // schedule wrapping image in resizable after a delay,
567 // schedule wrapping image in resizable after a delay,
567 // so we don't end up calling resize on a zero-size object
568 // so we don't end up calling resize on a zero-size object
568 var that = this;
569 var that = this;
569 setTimeout(function () {
570 setTimeout(function () {
570 var h0 = img.height();
571 var h0 = img.height();
571 var w0 = img.width();
572 var w0 = img.width();
572 if (!(h0 && w0)) {
573 if (!(h0 && w0)) {
573 // zero size, schedule another timeout
574 // zero size, schedule another timeout
574 that._dblclick_to_reset_size(img);
575 that._dblclick_to_reset_size(img);
575 return;
576 return;
576 }
577 }
577 img.resizable({
578 img.resizable({
578 aspectRatio: true,
579 aspectRatio: true,
579 autoHide: true
580 autoHide: true
580 });
581 });
581 img.dblclick(function () {
582 img.dblclick(function () {
582 // resize wrapper & image together for some reason:
583 // resize wrapper & image together for some reason:
583 img.parent().height(h0);
584 img.parent().height(h0);
584 img.height(h0);
585 img.height(h0);
585 img.parent().width(w0);
586 img.parent().width(w0);
586 img.width(w0);
587 img.width(w0);
587 });
588 });
588 }, 250);
589 }, 250);
589 };
590 };
590
591
591 var set_width_height = function (img, md, mime) {
592 var set_width_height = function (img, md, mime) {
592 // set width and height of an img element from metadata
593 // set width and height of an img element from metadata
593 var height = _get_metadata_key(md, 'height', mime);
594 var height = _get_metadata_key(md, 'height', mime);
594 if (height !== undefined) img.attr('height', height);
595 if (height !== undefined) img.attr('height', height);
595 var width = _get_metadata_key(md, 'width', mime);
596 var width = _get_metadata_key(md, 'width', mime);
596 if (width !== undefined) img.attr('width', width);
597 if (width !== undefined) img.attr('width', width);
597 };
598 };
598
599
599 OutputArea.prototype.append_png = function (png, md, element) {
600 OutputArea.prototype.append_png = function (png, md, element) {
600 var type = 'image/png';
601 var type = 'image/png';
601 var toinsert = this.create_output_subarea(md, "output_png", type);
602 var toinsert = this.create_output_subarea(md, "output_png", type);
602 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
603 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
603 set_width_height(img, md, 'image/png');
604 set_width_height(img, md, 'image/png');
604 this._dblclick_to_reset_size(img);
605 this._dblclick_to_reset_size(img);
605 toinsert.append(img);
606 toinsert.append(img);
606 element.append(toinsert);
607 element.append(toinsert);
607 return toinsert;
608 return toinsert;
608 };
609 };
609
610
610
611
611 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
612 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
612 var type = 'image/jpeg';
613 var type = 'image/jpeg';
613 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
614 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
614 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
615 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
615 set_width_height(img, md, 'image/jpeg');
616 set_width_height(img, md, 'image/jpeg');
616 this._dblclick_to_reset_size(img);
617 this._dblclick_to_reset_size(img);
617 toinsert.append(img);
618 toinsert.append(img);
618 element.append(toinsert);
619 element.append(toinsert);
619 return toinsert;
620 return toinsert;
620 };
621 };
621
622
622
623
624 OutputArea.prototype.append_pdf = function (pdf, md, element) {
625 var type = 'application/pdf';
626 var toinsert = this.create_output_subarea(md, "output_pdf", type);
627 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
628 a.attr('target', '_blank');
629 a.text('View PDF')
630 toinsert.append(a);
631 element.append(toinsert);
632 return toinsert;
633 }
634
623 OutputArea.prototype.append_latex = function (latex, md, element) {
635 OutputArea.prototype.append_latex = function (latex, md, element) {
624 // This method cannot do the typesetting because the latex first has to
636 // This method cannot do the typesetting because the latex first has to
625 // be on the page.
637 // be on the page.
626 var type = 'text/latex';
638 var type = 'text/latex';
627 var toinsert = this.create_output_subarea(md, "output_latex", type);
639 var toinsert = this.create_output_subarea(md, "output_latex", type);
628 toinsert.append(latex);
640 toinsert.append(latex);
629 element.append(toinsert);
641 element.append(toinsert);
630 return toinsert;
642 return toinsert;
631 };
643 };
632
644
633
645
634 OutputArea.prototype.append_raw_input = function (msg) {
646 OutputArea.prototype.append_raw_input = function (msg) {
635 var that = this;
647 var that = this;
636 this.expand();
648 this.expand();
637 var content = msg.content;
649 var content = msg.content;
638 var area = this.create_output_area();
650 var area = this.create_output_area();
639
651
640 // disable any other raw_inputs, if they are left around
652 // disable any other raw_inputs, if they are left around
641 $("div.output_subarea.raw_input").remove();
653 $("div.output_subarea.raw_input").remove();
642
654
643 area.append(
655 area.append(
644 $("<div/>")
656 $("<div/>")
645 .addClass("box-flex1 output_subarea raw_input")
657 .addClass("box-flex1 output_subarea raw_input")
646 .append(
658 .append(
647 $("<span/>")
659 $("<span/>")
648 .addClass("input_prompt")
660 .addClass("input_prompt")
649 .text(content.prompt)
661 .text(content.prompt)
650 )
662 )
651 .append(
663 .append(
652 $("<input/>")
664 $("<input/>")
653 .addClass("raw_input")
665 .addClass("raw_input")
654 .attr('type', 'text')
666 .attr('type', 'text')
655 .attr("size", 47)
667 .attr("size", 47)
656 .keydown(function (event, ui) {
668 .keydown(function (event, ui) {
657 // make sure we submit on enter,
669 // make sure we submit on enter,
658 // and don't re-execute the *cell* on shift-enter
670 // and don't re-execute the *cell* on shift-enter
659 if (event.which === utils.keycodes.ENTER) {
671 if (event.which === utils.keycodes.ENTER) {
660 that._submit_raw_input();
672 that._submit_raw_input();
661 return false;
673 return false;
662 }
674 }
663 })
675 })
664 )
676 )
665 );
677 );
666
678
667 this.element.append(area);
679 this.element.append(area);
668 var raw_input = area.find('input.raw_input');
680 var raw_input = area.find('input.raw_input');
669 // Register events that enable/disable the keyboard manager while raw
681 // Register events that enable/disable the keyboard manager while raw
670 // input is focused.
682 // input is focused.
671 IPython.keyboard_manager.register_events(raw_input);
683 IPython.keyboard_manager.register_events(raw_input);
672 // Note, the following line used to read raw_input.focus().focus().
684 // Note, the following line used to read raw_input.focus().focus().
673 // This seemed to be needed otherwise only the cell would be focused.
685 // This seemed to be needed otherwise only the cell would be focused.
674 // But with the modal UI, this seems to work fine with one call to focus().
686 // But with the modal UI, this seems to work fine with one call to focus().
675 raw_input.focus();
687 raw_input.focus();
676 }
688 }
677
689
678 OutputArea.prototype._submit_raw_input = function (evt) {
690 OutputArea.prototype._submit_raw_input = function (evt) {
679 var container = this.element.find("div.raw_input");
691 var container = this.element.find("div.raw_input");
680 var theprompt = container.find("span.input_prompt");
692 var theprompt = container.find("span.input_prompt");
681 var theinput = container.find("input.raw_input");
693 var theinput = container.find("input.raw_input");
682 var value = theinput.val();
694 var value = theinput.val();
683 var content = {
695 var content = {
684 output_type : 'stream',
696 output_type : 'stream',
685 name : 'stdout',
697 name : 'stdout',
686 text : theprompt.text() + value + '\n'
698 text : theprompt.text() + value + '\n'
687 }
699 }
688 // remove form container
700 // remove form container
689 container.parent().remove();
701 container.parent().remove();
690 // replace with plaintext version in stdout
702 // replace with plaintext version in stdout
691 this.append_output(content, false);
703 this.append_output(content, false);
692 $([IPython.events]).trigger('send_input_reply.Kernel', value);
704 $([IPython.events]).trigger('send_input_reply.Kernel', value);
693 }
705 }
694
706
695
707
696 OutputArea.prototype.handle_clear_output = function (msg) {
708 OutputArea.prototype.handle_clear_output = function (msg) {
697 // msg spec v4 had stdout, stderr, display keys
709 // msg spec v4 had stdout, stderr, display keys
698 // v4.1 replaced these with just wait
710 // v4.1 replaced these with just wait
699 // The default behavior is the same (stdout=stderr=display=True, wait=False),
711 // The default behavior is the same (stdout=stderr=display=True, wait=False),
700 // so v4 messages will still be properly handled,
712 // so v4 messages will still be properly handled,
701 // except for the rarely used clearing less than all output.
713 // except for the rarely used clearing less than all output.
702 this.clear_output(msg.content.wait || false);
714 this.clear_output(msg.content.wait || false);
703 };
715 };
704
716
705
717
706 OutputArea.prototype.clear_output = function(wait) {
718 OutputArea.prototype.clear_output = function(wait) {
707 if (wait) {
719 if (wait) {
708
720
709 // If a clear is queued, clear before adding another to the queue.
721 // If a clear is queued, clear before adding another to the queue.
710 if (this.clear_queued) {
722 if (this.clear_queued) {
711 this.clear_output(false);
723 this.clear_output(false);
712 };
724 };
713
725
714 this.clear_queued = true;
726 this.clear_queued = true;
715 } else {
727 } else {
716
728
717 // Fix the output div's height if the clear_output is waiting for
729 // Fix the output div's height if the clear_output is waiting for
718 // new output (it is being used in an animation).
730 // new output (it is being used in an animation).
719 if (this.clear_queued) {
731 if (this.clear_queued) {
720 var height = this.element.height();
732 var height = this.element.height();
721 this.element.height(height);
733 this.element.height(height);
722 this.clear_queued = false;
734 this.clear_queued = false;
723 }
735 }
724
736
725 // clear all, no need for logic
737 // clear all, no need for logic
726 this.element.html("");
738 this.element.html("");
727 this.outputs = [];
739 this.outputs = [];
728 this.trusted = true;
740 this.trusted = true;
729 this.unscroll_area();
741 this.unscroll_area();
730 return;
742 return;
731 };
743 };
732 };
744 };
733
745
734
746
735 // JSON serialization
747 // JSON serialization
736
748
737 OutputArea.prototype.fromJSON = function (outputs) {
749 OutputArea.prototype.fromJSON = function (outputs) {
738 var len = outputs.length;
750 var len = outputs.length;
739 var data;
751 var data;
740
752
741 for (var i=0; i<len; i++) {
753 for (var i=0; i<len; i++) {
742 data = outputs[i];
754 data = outputs[i];
743 var msg_type = data.output_type;
755 var msg_type = data.output_type;
744 if (msg_type === "display_data" || msg_type === "pyout") {
756 if (msg_type === "display_data" || msg_type === "pyout") {
745 // convert short keys to mime keys
757 // convert short keys to mime keys
746 // TODO: remove mapping of short keys when we update to nbformat 4
758 // TODO: remove mapping of short keys when we update to nbformat 4
747 data = this.rename_keys(data, OutputArea.mime_map_r);
759 data = this.rename_keys(data, OutputArea.mime_map_r);
748 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
760 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
749 }
761 }
750
762
751 this.append_output(data);
763 this.append_output(data);
752 }
764 }
753 };
765 };
754
766
755
767
756 OutputArea.prototype.toJSON = function () {
768 OutputArea.prototype.toJSON = function () {
757 var outputs = [];
769 var outputs = [];
758 var len = this.outputs.length;
770 var len = this.outputs.length;
759 var data;
771 var data;
760 for (var i=0; i<len; i++) {
772 for (var i=0; i<len; i++) {
761 data = this.outputs[i];
773 data = this.outputs[i];
762 var msg_type = data.output_type;
774 var msg_type = data.output_type;
763 if (msg_type === "display_data" || msg_type === "pyout") {
775 if (msg_type === "display_data" || msg_type === "pyout") {
764 // convert mime keys to short keys
776 // convert mime keys to short keys
765 data = this.rename_keys(data, OutputArea.mime_map);
777 data = this.rename_keys(data, OutputArea.mime_map);
766 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
778 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
767 }
779 }
768 outputs[i] = data;
780 outputs[i] = data;
769 }
781 }
770 return outputs;
782 return outputs;
771 };
783 };
772
784
773 /**
785 /**
774 * Class properties
786 * Class properties
775 **/
787 **/
776
788
777 /**
789 /**
778 * Threshold to trigger autoscroll when the OutputArea is resized,
790 * Threshold to trigger autoscroll when the OutputArea is resized,
779 * typically when new outputs are added.
791 * typically when new outputs are added.
780 *
792 *
781 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
793 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
782 * unless it is < 0, in which case autoscroll will never be triggered
794 * unless it is < 0, in which case autoscroll will never be triggered
783 *
795 *
784 * @property auto_scroll_threshold
796 * @property auto_scroll_threshold
785 * @type Number
797 * @type Number
786 * @default 100
798 * @default 100
787 *
799 *
788 **/
800 **/
789 OutputArea.auto_scroll_threshold = 100;
801 OutputArea.auto_scroll_threshold = 100;
790
802
791 /**
803 /**
792 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
804 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
793 * shorter than this are never scrolled.
805 * shorter than this are never scrolled.
794 *
806 *
795 * @property minimum_scroll_threshold
807 * @property minimum_scroll_threshold
796 * @type Number
808 * @type Number
797 * @default 20
809 * @default 20
798 *
810 *
799 **/
811 **/
800 OutputArea.minimum_scroll_threshold = 20;
812 OutputArea.minimum_scroll_threshold = 20;
801
813
802
814
803
815
804 OutputArea.mime_map = {
816 OutputArea.mime_map = {
805 "text/plain" : "text",
817 "text/plain" : "text",
806 "text/html" : "html",
818 "text/html" : "html",
807 "image/svg+xml" : "svg",
819 "image/svg+xml" : "svg",
808 "image/png" : "png",
820 "image/png" : "png",
809 "image/jpeg" : "jpeg",
821 "image/jpeg" : "jpeg",
822 "application/pdf" : "pdf",
810 "text/latex" : "latex",
823 "text/latex" : "latex",
811 "application/json" : "json",
824 "application/json" : "json",
812 "application/javascript" : "javascript",
825 "application/javascript" : "javascript",
813 };
826 };
814
827
815 OutputArea.mime_map_r = {
828 OutputArea.mime_map_r = {
816 "text" : "text/plain",
829 "text" : "text/plain",
817 "html" : "text/html",
830 "html" : "text/html",
818 "svg" : "image/svg+xml",
831 "svg" : "image/svg+xml",
819 "png" : "image/png",
832 "png" : "image/png",
820 "jpeg" : "image/jpeg",
833 "jpeg" : "image/jpeg",
834 "pdf" : "application/pdf",
821 "latex" : "text/latex",
835 "latex" : "text/latex",
822 "json" : "application/json",
836 "json" : "application/json",
823 "javascript" : "application/javascript",
837 "javascript" : "application/javascript",
824 };
838 };
825
839
826 OutputArea.display_order = [
840 OutputArea.display_order = [
827 'application/javascript',
841 'application/javascript',
828 'text/html',
842 'text/html',
829 'text/latex',
843 'text/latex',
830 'image/svg+xml',
844 'image/svg+xml',
831 'image/png',
845 'image/png',
832 'image/jpeg',
846 'image/jpeg',
847 'application/pdf',
833 'text/plain'
848 'text/plain'
834 ];
849 ];
835
850
836 OutputArea.append_map = {
851 OutputArea.append_map = {
837 "text/plain" : OutputArea.prototype.append_text,
852 "text/plain" : OutputArea.prototype.append_text,
838 "text/html" : OutputArea.prototype.append_html,
853 "text/html" : OutputArea.prototype.append_html,
839 "image/svg+xml" : OutputArea.prototype.append_svg,
854 "image/svg+xml" : OutputArea.prototype.append_svg,
840 "image/png" : OutputArea.prototype.append_png,
855 "image/png" : OutputArea.prototype.append_png,
841 "image/jpeg" : OutputArea.prototype.append_jpeg,
856 "image/jpeg" : OutputArea.prototype.append_jpeg,
842 "text/latex" : OutputArea.prototype.append_latex,
857 "text/latex" : OutputArea.prototype.append_latex,
843 "application/json" : OutputArea.prototype.append_json,
858 "application/json" : OutputArea.prototype.append_json,
844 "application/javascript" : OutputArea.prototype.append_javascript,
859 "application/javascript" : OutputArea.prototype.append_javascript,
860 "application/pdf" : OutputArea.prototype.append_pdf
845 };
861 };
846
862
847 IPython.OutputArea = OutputArea;
863 IPython.OutputArea = OutputArea;
848
864
849 return IPython;
865 return IPython;
850
866
851 }(IPython));
867 }(IPython));
@@ -1,108 +1,115 b''
1 """Configurable for configuring the IPython inline backend
1 """Configurable for configuring the IPython inline backend
2
2
3 This module does not import anything from matplotlib.
3 This module does not import anything from matplotlib.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2011 The IPython Development Team
6 # Copyright (C) 2011 The IPython Development Team
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 from IPython.config.configurable import SingletonConfigurable
16 from IPython.config.configurable import SingletonConfigurable
17 from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, Bool, Int, TraitError
17 from IPython.utils.traitlets import (
18 Dict, Instance, CaselessStrEnum, Set, Bool, Int, TraitError, Unicode
19 )
18 from IPython.utils.warn import warn
20 from IPython.utils.warn import warn
19
21
20 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
21 # Configurable for inline backend options
23 # Configurable for inline backend options
22 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
23
25
24 def pil_available():
26 def pil_available():
25 """Test if PIL/Pillow is available"""
27 """Test if PIL/Pillow is available"""
26 out = False
28 out = False
27 try:
29 try:
28 from PIL import Image
30 from PIL import Image
29 out = True
31 out = True
30 except:
32 except:
31 pass
33 pass
32 return out
34 return out
33
35
34 # inherit from InlineBackendConfig for deprecation purposes
36 # inherit from InlineBackendConfig for deprecation purposes
35 class InlineBackendConfig(SingletonConfigurable):
37 class InlineBackendConfig(SingletonConfigurable):
36 pass
38 pass
37
39
38 class InlineBackend(InlineBackendConfig):
40 class InlineBackend(InlineBackendConfig):
39 """An object to store configuration of the inline backend."""
41 """An object to store configuration of the inline backend."""
40
42
41 def _config_changed(self, name, old, new):
43 def _config_changed(self, name, old, new):
42 # warn on change of renamed config section
44 # warn on change of renamed config section
43 if new.InlineBackendConfig != old.InlineBackendConfig:
45 if new.InlineBackendConfig != old.InlineBackendConfig:
44 warn("InlineBackendConfig has been renamed to InlineBackend")
46 warn("InlineBackendConfig has been renamed to InlineBackend")
45 super(InlineBackend, self)._config_changed(name, old, new)
47 super(InlineBackend, self)._config_changed(name, old, new)
46
48
47 # The typical default figure size is too large for inline use,
49 # The typical default figure size is too large for inline use,
48 # so we shrink the figure size to 6x4, and tweak fonts to
50 # so we shrink the figure size to 6x4, and tweak fonts to
49 # make that fit.
51 # make that fit.
50 rc = Dict({'figure.figsize': (6.0,4.0),
52 rc = Dict({'figure.figsize': (6.0,4.0),
51 # play nicely with white background in the Qt and notebook frontend
53 # play nicely with white background in the Qt and notebook frontend
52 'figure.facecolor': (1,1,1,0),
54 'figure.facecolor': (1,1,1,0),
53 'figure.edgecolor': (1,1,1,0),
55 'figure.edgecolor': (1,1,1,0),
54 # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
56 # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
55 'font.size': 10,
57 'font.size': 10,
56 # 72 dpi matches SVG/qtconsole
58 # 72 dpi matches SVG/qtconsole
57 # this only affects PNG export, as SVG has no dpi setting
59 # this only affects PNG export, as SVG has no dpi setting
58 'savefig.dpi': 72,
60 'savefig.dpi': 72,
59 # 10pt still needs a little more room on the xlabel:
61 # 10pt still needs a little more room on the xlabel:
60 'figure.subplot.bottom' : .125
62 'figure.subplot.bottom' : .125
61 }, config=True,
63 }, config=True,
62 help="""Subset of matplotlib rcParams that should be different for the
64 help="""Subset of matplotlib rcParams that should be different for the
63 inline backend."""
65 inline backend."""
64 )
66 )
65
67
68 figure_formats = Set({'png'}, config=True,
69 help="""A set of figure formats to enable: 'png',
70 'retina', 'jpeg', 'svg', 'pdf'.""")
66
71
67 figure_format = CaselessStrEnum(['svg', 'png', 'retina', 'jpg'],
72 def _figure_formats_changed(self, name, old, new):
68 default_value='png', config=True,
73 from IPython.core.pylabtools import select_figure_formats
69 help="""The image format for figures with the inline
74 if 'jpg' in new or 'jpeg' in new:
70 backend. JPEG requires the PIL/Pillow library.""")
71
72 def _figure_format_changed(self, name, old, new):
73 from IPython.core.pylabtools import select_figure_format
74 if new in {"jpg", "jpeg"}:
75 if not pil_available():
75 if not pil_available():
76 raise TraitError("Requires PIL/Pillow for JPG figures")
76 raise TraitError("Requires PIL/Pillow for JPG figures")
77 if self.shell is None:
77 if self.shell is None:
78 return
78 return
79 else:
79 else:
80 select_figure_format(self.shell, new)
80 select_figure_formats(self.shell, new)
81
82 figure_format = Unicode(config=True, help="""The figure format to enable (deprecated
83 use `figure_formats` instead)""")
84
85 def _figure_format_changed(self, name, old, new):
86 if new:
87 self.figure_formats = {new}
81
88
82 quality = Int(default_value=90, config=True,
89 quality = Int(default_value=90, config=True,
83 help="Quality of compression [10-100], currently for lossy JPEG only.")
90 help="Quality of compression [10-100], currently for lossy JPEG only.")
84
91
85 def _quality_changed(self, name, old, new):
92 def _quality_changed(self, name, old, new):
86 if new < 10 or new > 100:
93 if new < 10 or new > 100:
87 raise TraitError("figure JPEG quality must be in [10-100] range.")
94 raise TraitError("figure JPEG quality must be in [10-100] range.")
88
95
89 close_figures = Bool(True, config=True,
96 close_figures = Bool(True, config=True,
90 help="""Close all figures at the end of each cell.
97 help="""Close all figures at the end of each cell.
91
98
92 When True, ensures that each cell starts with no active figures, but it
99 When True, ensures that each cell starts with no active figures, but it
93 also means that one must keep track of references in order to edit or
100 also means that one must keep track of references in order to edit or
94 redraw figures in subsequent cells. This mode is ideal for the notebook,
101 redraw figures in subsequent cells. This mode is ideal for the notebook,
95 where residual plots from other cells might be surprising.
102 where residual plots from other cells might be surprising.
96
103
97 When False, one must call figure() to create new figures. This means
104 When False, one must call figure() to create new figures. This means
98 that gcf() and getfigs() can reference figures created in other cells,
105 that gcf() and getfigs() can reference figures created in other cells,
99 and the active figure can continue to be edited with pylab/pyplot
106 and the active figure can continue to be edited with pylab/pyplot
100 methods that reference the current active figure. This mode facilitates
107 methods that reference the current active figure. This mode facilitates
101 iterative editing of figures, and behaves most consistently with
108 iterative editing of figures, and behaves most consistently with
102 other matplotlib backends, but figure barriers between cells must
109 other matplotlib backends, but figure barriers between cells must
103 be explicit.
110 be explicit.
104 """)
111 """)
105
112
106 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
113 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
107
114
108
115
@@ -1,247 +1,256 b''
1 """Utilities to manipulate JSON objects.
1 """Utilities to manipulate JSON objects.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010-2011 The IPython Development Team
4 # Copyright (C) 2010-2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # stdlib
13 # stdlib
14 import math
14 import math
15 import re
15 import re
16 import types
16 import types
17 from datetime import datetime
17 from datetime import datetime
18
18
19 try:
19 try:
20 # base64.encodestring is deprecated in Python 3.x
20 # base64.encodestring is deprecated in Python 3.x
21 from base64 import encodebytes
21 from base64 import encodebytes
22 except ImportError:
22 except ImportError:
23 # Python 2.x
23 # Python 2.x
24 from base64 import encodestring as encodebytes
24 from base64 import encodestring as encodebytes
25
25
26 from IPython.utils import py3compat
26 from IPython.utils import py3compat
27 from IPython.utils.py3compat import string_types, unicode_type, iteritems
27 from IPython.utils.py3compat import string_types, unicode_type, iteritems
28 from IPython.utils.encoding import DEFAULT_ENCODING
28 from IPython.utils.encoding import DEFAULT_ENCODING
29 next_attr_name = '__next__' if py3compat.PY3 else 'next'
29 next_attr_name = '__next__' if py3compat.PY3 else 'next'
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Globals and constants
32 # Globals and constants
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 # timestamp formats
35 # timestamp formats
36 ISO8601 = "%Y-%m-%dT%H:%M:%S.%f"
36 ISO8601 = "%Y-%m-%dT%H:%M:%S.%f"
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})?$")
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 # Classes and functions
40 # Classes and functions
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 def rekey(dikt):
43 def rekey(dikt):
44 """Rekey a dict that has been forced to use str keys where there should be
44 """Rekey a dict that has been forced to use str keys where there should be
45 ints by json."""
45 ints by json."""
46 for k in dikt:
46 for k in dikt:
47 if isinstance(k, string_types):
47 if isinstance(k, string_types):
48 ik=fk=None
48 ik=fk=None
49 try:
49 try:
50 ik = int(k)
50 ik = int(k)
51 except ValueError:
51 except ValueError:
52 try:
52 try:
53 fk = float(k)
53 fk = float(k)
54 except ValueError:
54 except ValueError:
55 continue
55 continue
56 if ik is not None:
56 if ik is not None:
57 nk = ik
57 nk = ik
58 else:
58 else:
59 nk = fk
59 nk = fk
60 if nk in dikt:
60 if nk in dikt:
61 raise KeyError("already have key %r"%nk)
61 raise KeyError("already have key %r"%nk)
62 dikt[nk] = dikt.pop(k)
62 dikt[nk] = dikt.pop(k)
63 return dikt
63 return dikt
64
64
65 def parse_date(s):
65 def parse_date(s):
66 """parse an ISO8601 date string
66 """parse an ISO8601 date string
67
67
68 If it is None or not a valid ISO8601 timestamp,
68 If it is None or not a valid ISO8601 timestamp,
69 it will be returned unmodified.
69 it will be returned unmodified.
70 Otherwise, it will return a datetime object.
70 Otherwise, it will return a datetime object.
71 """
71 """
72 if s is None:
72 if s is None:
73 return s
73 return s
74 m = ISO8601_PAT.match(s)
74 m = ISO8601_PAT.match(s)
75 if m:
75 if m:
76 # FIXME: add actual timezone support
76 # FIXME: add actual timezone support
77 # this just drops the timezone info
77 # this just drops the timezone info
78 notz = m.groups()[0]
78 notz = m.groups()[0]
79 return datetime.strptime(notz, ISO8601)
79 return datetime.strptime(notz, ISO8601)
80 return s
80 return s
81
81
82 def extract_dates(obj):
82 def extract_dates(obj):
83 """extract ISO8601 dates from unpacked JSON"""
83 """extract ISO8601 dates from unpacked JSON"""
84 if isinstance(obj, dict):
84 if isinstance(obj, dict):
85 new_obj = {} # don't clobber
85 new_obj = {} # don't clobber
86 for k,v in iteritems(obj):
86 for k,v in iteritems(obj):
87 new_obj[k] = extract_dates(v)
87 new_obj[k] = extract_dates(v)
88 obj = new_obj
88 obj = new_obj
89 elif isinstance(obj, (list, tuple)):
89 elif isinstance(obj, (list, tuple)):
90 obj = [ extract_dates(o) for o in obj ]
90 obj = [ extract_dates(o) for o in obj ]
91 elif isinstance(obj, string_types):
91 elif isinstance(obj, string_types):
92 obj = parse_date(obj)
92 obj = parse_date(obj)
93 return obj
93 return obj
94
94
95 def squash_dates(obj):
95 def squash_dates(obj):
96 """squash datetime objects into ISO8601 strings"""
96 """squash datetime objects into ISO8601 strings"""
97 if isinstance(obj, dict):
97 if isinstance(obj, dict):
98 obj = dict(obj) # don't clobber
98 obj = dict(obj) # don't clobber
99 for k,v in iteritems(obj):
99 for k,v in iteritems(obj):
100 obj[k] = squash_dates(v)
100 obj[k] = squash_dates(v)
101 elif isinstance(obj, (list, tuple)):
101 elif isinstance(obj, (list, tuple)):
102 obj = [ squash_dates(o) for o in obj ]
102 obj = [ squash_dates(o) for o in obj ]
103 elif isinstance(obj, datetime):
103 elif isinstance(obj, datetime):
104 obj = obj.isoformat()
104 obj = obj.isoformat()
105 return obj
105 return obj
106
106
107 def date_default(obj):
107 def date_default(obj):
108 """default function for packing datetime objects in JSON."""
108 """default function for packing datetime objects in JSON."""
109 if isinstance(obj, datetime):
109 if isinstance(obj, datetime):
110 return obj.isoformat()
110 return obj.isoformat()
111 else:
111 else:
112 raise TypeError("%r is not JSON serializable"%obj)
112 raise TypeError("%r is not JSON serializable"%obj)
113
113
114
114
115 # constants for identifying png/jpeg data
115 # constants for identifying png/jpeg data
116 PNG = b'\x89PNG\r\n\x1a\n'
116 PNG = b'\x89PNG\r\n\x1a\n'
117 # front of PNG base64-encoded
117 # front of PNG base64-encoded
118 PNG64 = b'iVBORw0KG'
118 PNG64 = b'iVBORw0KG'
119 JPEG = b'\xff\xd8'
119 JPEG = b'\xff\xd8'
120 # front of JPEG base64-encoded
120 # front of JPEG base64-encoded
121 JPEG64 = b'/9'
121 JPEG64 = b'/9'
122 # front of PDF base64-encoded
123 PDF64 = b'JVBER'
122
124
123 def encode_images(format_dict):
125 def encode_images(format_dict):
124 """b64-encodes images in a displaypub format dict
126 """b64-encodes images in a displaypub format dict
125
127
126 Perhaps this should be handled in json_clean itself?
128 Perhaps this should be handled in json_clean itself?
127
129
128 Parameters
130 Parameters
129 ----------
131 ----------
130
132
131 format_dict : dict
133 format_dict : dict
132 A dictionary of display data keyed by mime-type
134 A dictionary of display data keyed by mime-type
133
135
134 Returns
136 Returns
135 -------
137 -------
136
138
137 format_dict : dict
139 format_dict : dict
138 A copy of the same dictionary,
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 is base64-encoded.
142 is base64-encoded.
141
143
142 """
144 """
143 encoded = format_dict.copy()
145 encoded = format_dict.copy()
144
146
145 pngdata = format_dict.get('image/png')
147 pngdata = format_dict.get('image/png')
146 if isinstance(pngdata, bytes):
148 if isinstance(pngdata, bytes):
147 # make sure we don't double-encode
149 # make sure we don't double-encode
148 if not pngdata.startswith(PNG64):
150 if not pngdata.startswith(PNG64):
149 pngdata = encodebytes(pngdata)
151 pngdata = encodebytes(pngdata)
150 encoded['image/png'] = pngdata.decode('ascii')
152 encoded['image/png'] = pngdata.decode('ascii')
151
153
152 jpegdata = format_dict.get('image/jpeg')
154 jpegdata = format_dict.get('image/jpeg')
153 if isinstance(jpegdata, bytes):
155 if isinstance(jpegdata, bytes):
154 # make sure we don't double-encode
156 # make sure we don't double-encode
155 if not jpegdata.startswith(JPEG64):
157 if not jpegdata.startswith(JPEG64):
156 jpegdata = encodebytes(jpegdata)
158 jpegdata = encodebytes(jpegdata)
157 encoded['image/jpeg'] = jpegdata.decode('ascii')
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 return encoded
168 return encoded
160
169
161
170
162 def json_clean(obj):
171 def json_clean(obj):
163 """Clean an object to ensure it's safe to encode in JSON.
172 """Clean an object to ensure it's safe to encode in JSON.
164
173
165 Atomic, immutable objects are returned unmodified. Sets and tuples are
174 Atomic, immutable objects are returned unmodified. Sets and tuples are
166 converted to lists, lists are copied and dicts are also copied.
175 converted to lists, lists are copied and dicts are also copied.
167
176
168 Note: dicts whose keys could cause collisions upon encoding (such as a dict
177 Note: dicts whose keys could cause collisions upon encoding (such as a dict
169 with both the number 1 and the string '1' as keys) will cause a ValueError
178 with both the number 1 and the string '1' as keys) will cause a ValueError
170 to be raised.
179 to be raised.
171
180
172 Parameters
181 Parameters
173 ----------
182 ----------
174 obj : any python object
183 obj : any python object
175
184
176 Returns
185 Returns
177 -------
186 -------
178 out : object
187 out : object
179
188
180 A version of the input which will not cause an encoding error when
189 A version of the input which will not cause an encoding error when
181 encoded as JSON. Note that this function does not *encode* its inputs,
190 encoded as JSON. Note that this function does not *encode* its inputs,
182 it simply sanitizes it so that there will be no encoding errors later.
191 it simply sanitizes it so that there will be no encoding errors later.
183
192
184 Examples
193 Examples
185 --------
194 --------
186 >>> json_clean(4)
195 >>> json_clean(4)
187 4
196 4
188 >>> json_clean(list(range(10)))
197 >>> json_clean(list(range(10)))
189 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
198 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
190 >>> sorted(json_clean(dict(x=1, y=2)).items())
199 >>> sorted(json_clean(dict(x=1, y=2)).items())
191 [('x', 1), ('y', 2)]
200 [('x', 1), ('y', 2)]
192 >>> sorted(json_clean(dict(x=1, y=2, z=[1,2,3])).items())
201 >>> sorted(json_clean(dict(x=1, y=2, z=[1,2,3])).items())
193 [('x', 1), ('y', 2), ('z', [1, 2, 3])]
202 [('x', 1), ('y', 2), ('z', [1, 2, 3])]
194 >>> json_clean(True)
203 >>> json_clean(True)
195 True
204 True
196 """
205 """
197 # types that are 'atomic' and ok in json as-is.
206 # types that are 'atomic' and ok in json as-is.
198 atomic_ok = (unicode_type, type(None))
207 atomic_ok = (unicode_type, type(None))
199
208
200 # containers that we need to convert into lists
209 # containers that we need to convert into lists
201 container_to_list = (tuple, set, types.GeneratorType)
210 container_to_list = (tuple, set, types.GeneratorType)
202
211
203 if isinstance(obj, float):
212 if isinstance(obj, float):
204 # cast out-of-range floats to their reprs
213 # cast out-of-range floats to their reprs
205 if math.isnan(obj) or math.isinf(obj):
214 if math.isnan(obj) or math.isinf(obj):
206 return repr(obj)
215 return repr(obj)
207 return float(obj)
216 return float(obj)
208
217
209 if isinstance(obj, int):
218 if isinstance(obj, int):
210 # cast int to int, in case subclasses override __str__ (e.g. boost enum, #4598)
219 # cast int to int, in case subclasses override __str__ (e.g. boost enum, #4598)
211 if isinstance(obj, bool):
220 if isinstance(obj, bool):
212 # bools are ints, but we don't want to cast them to 0,1
221 # bools are ints, but we don't want to cast them to 0,1
213 return obj
222 return obj
214 return int(obj)
223 return int(obj)
215
224
216 if isinstance(obj, atomic_ok):
225 if isinstance(obj, atomic_ok):
217 return obj
226 return obj
218
227
219 if isinstance(obj, bytes):
228 if isinstance(obj, bytes):
220 return obj.decode(DEFAULT_ENCODING, 'replace')
229 return obj.decode(DEFAULT_ENCODING, 'replace')
221
230
222 if isinstance(obj, container_to_list) or (
231 if isinstance(obj, container_to_list) or (
223 hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)):
232 hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)):
224 obj = list(obj)
233 obj = list(obj)
225
234
226 if isinstance(obj, list):
235 if isinstance(obj, list):
227 return [json_clean(x) for x in obj]
236 return [json_clean(x) for x in obj]
228
237
229 if isinstance(obj, dict):
238 if isinstance(obj, dict):
230 # First, validate that the dict won't lose data in conversion due to
239 # First, validate that the dict won't lose data in conversion due to
231 # key collisions after stringification. This can happen with keys like
240 # key collisions after stringification. This can happen with keys like
232 # True and 'true' or 1 and '1', which collide in JSON.
241 # True and 'true' or 1 and '1', which collide in JSON.
233 nkeys = len(obj)
242 nkeys = len(obj)
234 nkeys_collapsed = len(set(map(str, obj)))
243 nkeys_collapsed = len(set(map(str, obj)))
235 if nkeys != nkeys_collapsed:
244 if nkeys != nkeys_collapsed:
236 raise ValueError('dict can not be safely converted to JSON: '
245 raise ValueError('dict can not be safely converted to JSON: '
237 'key collision would lead to dropped values')
246 'key collision would lead to dropped values')
238 # If all OK, proceed by making the new dict that will be json-safe
247 # If all OK, proceed by making the new dict that will be json-safe
239 out = {}
248 out = {}
240 for k,v in iteritems(obj):
249 for k,v in iteritems(obj):
241 out[str(k)] = json_clean(v)
250 out[str(k)] = json_clean(v)
242 return out
251 return out
243
252
244 # If we get here, we don't know how to handle the object, so we just get
253 # If we get here, we don't know how to handle the object, so we just get
245 # its repr and return that. This will catch lambdas, open sockets, class
254 # its repr and return that. This will catch lambdas, open sockets, class
246 # objects, and any other complicated contraption that json can't encode
255 # objects, and any other complicated contraption that json can't encode
247 return repr(obj)
256 return repr(obj)
@@ -1,147 +1,149 b''
1 """Test suite for our JSON utilities.
1 """Test suite for our JSON utilities.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010-2011 The IPython Development Team
4 # Copyright (C) 2010-2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # stdlib
13 # stdlib
14 import datetime
14 import datetime
15 import json
15 import json
16 from base64 import decodestring
16 from base64 import decodestring
17
17
18 # third party
18 # third party
19 import nose.tools as nt
19 import nose.tools as nt
20
20
21 # our own
21 # our own
22 from IPython.utils import jsonutil, tz
22 from IPython.utils import jsonutil, tz
23 from ..jsonutil import json_clean, encode_images
23 from ..jsonutil import json_clean, encode_images
24 from ..py3compat import unicode_to_str, str_to_bytes, iteritems
24 from ..py3compat import unicode_to_str, str_to_bytes, iteritems
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Test functions
27 # Test functions
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 class Int(int):
29 class Int(int):
30 def __str__(self):
30 def __str__(self):
31 return 'Int(%i)' % self
31 return 'Int(%i)' % self
32
32
33 def test():
33 def test():
34 # list of input/expected output. Use None for the expected output if it
34 # list of input/expected output. Use None for the expected output if it
35 # can be the same as the input.
35 # can be the same as the input.
36 pairs = [(1, None), # start with scalars
36 pairs = [(1, None), # start with scalars
37 (1.0, None),
37 (1.0, None),
38 ('a', None),
38 ('a', None),
39 (True, None),
39 (True, None),
40 (False, None),
40 (False, None),
41 (None, None),
41 (None, None),
42 # complex numbers for now just go to strings, as otherwise they
42 # complex numbers for now just go to strings, as otherwise they
43 # are unserializable
43 # are unserializable
44 (1j, '1j'),
44 (1j, '1j'),
45 # Containers
45 # Containers
46 ([1, 2], None),
46 ([1, 2], None),
47 ((1, 2), [1, 2]),
47 ((1, 2), [1, 2]),
48 (set([1, 2]), [1, 2]),
48 (set([1, 2]), [1, 2]),
49 (dict(x=1), None),
49 (dict(x=1), None),
50 ({'x': 1, 'y':[1,2,3], '1':'int'}, None),
50 ({'x': 1, 'y':[1,2,3], '1':'int'}, None),
51 # More exotic objects
51 # More exotic objects
52 ((x for x in range(3)), [0, 1, 2]),
52 ((x for x in range(3)), [0, 1, 2]),
53 (iter([1, 2]), [1, 2]),
53 (iter([1, 2]), [1, 2]),
54 (Int(5), 5),
54 (Int(5), 5),
55 ]
55 ]
56
56
57 for val, jval in pairs:
57 for val, jval in pairs:
58 if jval is None:
58 if jval is None:
59 jval = val
59 jval = val
60 out = json_clean(val)
60 out = json_clean(val)
61 # validate our cleanup
61 # validate our cleanup
62 nt.assert_equal(out, jval)
62 nt.assert_equal(out, jval)
63 # and ensure that what we return, indeed encodes cleanly
63 # and ensure that what we return, indeed encodes cleanly
64 json.loads(json.dumps(out))
64 json.loads(json.dumps(out))
65
65
66
66
67
67
68 def test_encode_images():
68 def test_encode_images():
69 # invalid data, but the header and footer are from real files
69 # invalid data, but the header and footer are from real files
70 pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82'
70 pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82'
71 jpegdata = b'\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9'
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 fmt = {
74 fmt = {
74 'image/png' : pngdata,
75 'image/png' : pngdata,
75 'image/jpeg' : jpegdata,
76 'image/jpeg' : jpegdata,
77 'application/pdf' : pdfdata
76 }
78 }
77 encoded = encode_images(fmt)
79 encoded = encode_images(fmt)
78 for key, value in iteritems(fmt):
80 for key, value in iteritems(fmt):
79 # encoded has unicode, want bytes
81 # encoded has unicode, want bytes
80 decoded = decodestring(encoded[key].encode('ascii'))
82 decoded = decodestring(encoded[key].encode('ascii'))
81 nt.assert_equal(decoded, value)
83 nt.assert_equal(decoded, value)
82 encoded2 = encode_images(encoded)
84 encoded2 = encode_images(encoded)
83 nt.assert_equal(encoded, encoded2)
85 nt.assert_equal(encoded, encoded2)
84
86
85 b64_str = {}
87 b64_str = {}
86 for key, encoded in iteritems(encoded):
88 for key, encoded in iteritems(encoded):
87 b64_str[key] = unicode_to_str(encoded)
89 b64_str[key] = unicode_to_str(encoded)
88 encoded3 = encode_images(b64_str)
90 encoded3 = encode_images(b64_str)
89 nt.assert_equal(encoded3, b64_str)
91 nt.assert_equal(encoded3, b64_str)
90 for key, value in iteritems(fmt):
92 for key, value in iteritems(fmt):
91 # encoded3 has str, want bytes
93 # encoded3 has str, want bytes
92 decoded = decodestring(str_to_bytes(encoded3[key]))
94 decoded = decodestring(str_to_bytes(encoded3[key]))
93 nt.assert_equal(decoded, value)
95 nt.assert_equal(decoded, value)
94
96
95 def test_lambda():
97 def test_lambda():
96 jc = json_clean(lambda : 1)
98 jc = json_clean(lambda : 1)
97 assert isinstance(jc, str)
99 assert isinstance(jc, str)
98 assert '<lambda>' in jc
100 assert '<lambda>' in jc
99 json.dumps(jc)
101 json.dumps(jc)
100
102
101 def test_extract_dates():
103 def test_extract_dates():
102 timestamps = [
104 timestamps = [
103 '2013-07-03T16:34:52.249482',
105 '2013-07-03T16:34:52.249482',
104 '2013-07-03T16:34:52.249482Z',
106 '2013-07-03T16:34:52.249482Z',
105 '2013-07-03T16:34:52.249482Z-0800',
107 '2013-07-03T16:34:52.249482Z-0800',
106 '2013-07-03T16:34:52.249482Z+0800',
108 '2013-07-03T16:34:52.249482Z+0800',
107 '2013-07-03T16:34:52.249482Z+08:00',
109 '2013-07-03T16:34:52.249482Z+08:00',
108 '2013-07-03T16:34:52.249482Z-08:00',
110 '2013-07-03T16:34:52.249482Z-08:00',
109 '2013-07-03T16:34:52.249482-0800',
111 '2013-07-03T16:34:52.249482-0800',
110 '2013-07-03T16:34:52.249482+0800',
112 '2013-07-03T16:34:52.249482+0800',
111 '2013-07-03T16:34:52.249482+08:00',
113 '2013-07-03T16:34:52.249482+08:00',
112 '2013-07-03T16:34:52.249482-08:00',
114 '2013-07-03T16:34:52.249482-08:00',
113 ]
115 ]
114 extracted = jsonutil.extract_dates(timestamps)
116 extracted = jsonutil.extract_dates(timestamps)
115 ref = extracted[0]
117 ref = extracted[0]
116 for dt in extracted:
118 for dt in extracted:
117 nt.assert_true(isinstance(dt, datetime.datetime))
119 nt.assert_true(isinstance(dt, datetime.datetime))
118 nt.assert_equal(dt, ref)
120 nt.assert_equal(dt, ref)
119
121
120 def test_parse_ms_precision():
122 def test_parse_ms_precision():
121 base = '2013-07-03T16:34:52.'
123 base = '2013-07-03T16:34:52.'
122 digits = '1234567890'
124 digits = '1234567890'
123
125
124 for i in range(len(digits)):
126 for i in range(len(digits)):
125 ts = base + digits[:i]
127 ts = base + digits[:i]
126 parsed = jsonutil.parse_date(ts)
128 parsed = jsonutil.parse_date(ts)
127 if i >= 1 and i <= 6:
129 if i >= 1 and i <= 6:
128 assert isinstance(parsed, datetime.datetime)
130 assert isinstance(parsed, datetime.datetime)
129 else:
131 else:
130 assert isinstance(parsed, str)
132 assert isinstance(parsed, str)
131
133
132 def test_date_default():
134 def test_date_default():
133 data = dict(today=datetime.datetime.now(), utcnow=tz.utcnow())
135 data = dict(today=datetime.datetime.now(), utcnow=tz.utcnow())
134 jsondata = json.dumps(data, default=jsonutil.date_default)
136 jsondata = json.dumps(data, default=jsonutil.date_default)
135 nt.assert_in("+00", jsondata)
137 nt.assert_in("+00", jsondata)
136 nt.assert_equal(jsondata.count("+00"), 1)
138 nt.assert_equal(jsondata.count("+00"), 1)
137 extracted = jsonutil.extract_dates(json.loads(jsondata))
139 extracted = jsonutil.extract_dates(json.loads(jsondata))
138 for dt in extracted.values():
140 for dt in extracted.values():
139 nt.assert_true(isinstance(dt, datetime.datetime))
141 nt.assert_true(isinstance(dt, datetime.datetime))
140
142
141 def test_exception():
143 def test_exception():
142 bad_dicts = [{1:'number', '1':'string'},
144 bad_dicts = [{1:'number', '1':'string'},
143 {True:'bool', 'True':'string'},
145 {True:'bool', 'True':'string'},
144 ]
146 ]
145 for d in bad_dicts:
147 for d in bad_dicts:
146 nt.assert_raises(ValueError, json_clean, d)
148 nt.assert_raises(ValueError, json_clean, d)
147
149
General Comments 0
You need to be logged in to leave comments. Login now