##// END OF EJS Templates
Do not import from IPython.core.display and warn users.
Matthias Bussonnier -
Show More
@@ -1,1154 +1,1176 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 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 from binascii import b2a_base64, hexlify
8 from binascii import b2a_base64, hexlify
9 import json
9 import json
10 import mimetypes
10 import mimetypes
11 import os
11 import os
12 import struct
12 import struct
13 import warnings
13 import warnings
14 from copy import deepcopy
14 from copy import deepcopy
15 from os.path import splitext
15 from os.path import splitext
16 from pathlib import Path, PurePath
16 from pathlib import Path, PurePath
17
17
18 from IPython.utils.py3compat import cast_unicode
18 from IPython.utils.py3compat import cast_unicode
19 from IPython.testing.skipdoctest import skip_doctest
19 from IPython.testing.skipdoctest import skip_doctest
20 from .display_functions import display, clear_output, publish_display_data, update_display, DisplayHandle
20 from . import display_functions
21
21
22 __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown',
22
23 __all__ = ['display_pretty', 'display_html', 'display_markdown',
23 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
24 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
24 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
25 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
25 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON',
26 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON',
26 'GeoJSON', 'Javascript', 'Image', 'clear_output', 'set_matplotlib_formats',
27 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats',
27 'set_matplotlib_close', 'publish_display_data', 'update_display', 'DisplayHandle',
28 'set_matplotlib_close',
28 'Video']
29 'Video']
29
30
31 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
32
33 __all__ = __all__ + _deprecated_names
34
35
36 # ----- warn to import from IPython.display -----
37
38 from warnings import warn
39
40
41 def __getattr__(name):
42 if name in _deprecated_names:
43 warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2)
44 return getattr(display_functions, name)
45
46 if name in globals().keys():
47 return globals()[name]
48 else:
49 raise AttributeError(f"module {__name__} has no attribute {name}")
50
51
30 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
31 # utility functions
53 # utility functions
32 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
33
55
34 def _safe_exists(path):
56 def _safe_exists(path):
35 """Check path, but don't let exceptions raise"""
57 """Check path, but don't let exceptions raise"""
36 try:
58 try:
37 return os.path.exists(path)
59 return os.path.exists(path)
38 except Exception:
60 except Exception:
39 return False
61 return False
40
62
41
63
42 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
64 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
43 """internal implementation of all display_foo methods
65 """internal implementation of all display_foo methods
44
66
45 Parameters
67 Parameters
46 ----------
68 ----------
47 mimetype : str
69 mimetype : str
48 The mimetype to be published (e.g. 'image/png')
70 The mimetype to be published (e.g. 'image/png')
49 *objs : object
71 *objs : object
50 The Python objects to display, or if raw=True raw text data to
72 The Python objects to display, or if raw=True raw text data to
51 display.
73 display.
52 raw : bool
74 raw : bool
53 Are the data objects raw data or Python objects that need to be
75 Are the data objects raw data or Python objects that need to be
54 formatted before display? [default: False]
76 formatted before display? [default: False]
55 metadata : dict (optional)
77 metadata : dict (optional)
56 Metadata to be associated with the specific mimetype output.
78 Metadata to be associated with the specific mimetype output.
57 """
79 """
58 if metadata:
80 if metadata:
59 metadata = {mimetype: metadata}
81 metadata = {mimetype: metadata}
60 if raw:
82 if raw:
61 # turn list of pngdata into list of { 'image/png': pngdata }
83 # turn list of pngdata into list of { 'image/png': pngdata }
62 objs = [ {mimetype: obj} for obj in objs ]
84 objs = [ {mimetype: obj} for obj in objs ]
63 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
85 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
64
86
65 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
66 # Main functions
88 # Main functions
67 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
68
90
69
91
70 def display_pretty(*objs, **kwargs):
92 def display_pretty(*objs, **kwargs):
71 """Display the pretty (default) representation of an object.
93 """Display the pretty (default) representation of an object.
72
94
73 Parameters
95 Parameters
74 ----------
96 ----------
75 *objs : object
97 *objs : object
76 The Python objects to display, or if raw=True raw text data to
98 The Python objects to display, or if raw=True raw text data to
77 display.
99 display.
78 raw : bool
100 raw : bool
79 Are the data objects raw data or Python objects that need to be
101 Are the data objects raw data or Python objects that need to be
80 formatted before display? [default: False]
102 formatted before display? [default: False]
81 metadata : dict (optional)
103 metadata : dict (optional)
82 Metadata to be associated with the specific mimetype output.
104 Metadata to be associated with the specific mimetype output.
83 """
105 """
84 _display_mimetype('text/plain', objs, **kwargs)
106 _display_mimetype('text/plain', objs, **kwargs)
85
107
86
108
87 def display_html(*objs, **kwargs):
109 def display_html(*objs, **kwargs):
88 """Display the HTML representation of an object.
110 """Display the HTML representation of an object.
89
111
90 Note: If raw=False and the object does not have a HTML
112 Note: If raw=False and the object does not have a HTML
91 representation, no HTML will be shown.
113 representation, no HTML will be shown.
92
114
93 Parameters
115 Parameters
94 ----------
116 ----------
95 *objs : object
117 *objs : object
96 The Python objects to display, or if raw=True raw HTML data to
118 The Python objects to display, or if raw=True raw HTML data to
97 display.
119 display.
98 raw : bool
120 raw : bool
99 Are the data objects raw data or Python objects that need to be
121 Are the data objects raw data or Python objects that need to be
100 formatted before display? [default: False]
122 formatted before display? [default: False]
101 metadata : dict (optional)
123 metadata : dict (optional)
102 Metadata to be associated with the specific mimetype output.
124 Metadata to be associated with the specific mimetype output.
103 """
125 """
104 _display_mimetype('text/html', objs, **kwargs)
126 _display_mimetype('text/html', objs, **kwargs)
105
127
106
128
107 def display_markdown(*objs, **kwargs):
129 def display_markdown(*objs, **kwargs):
108 """Displays the Markdown representation of an object.
130 """Displays the Markdown representation of an object.
109
131
110 Parameters
132 Parameters
111 ----------
133 ----------
112 *objs : object
134 *objs : object
113 The Python objects to display, or if raw=True raw markdown data to
135 The Python objects to display, or if raw=True raw markdown data to
114 display.
136 display.
115 raw : bool
137 raw : bool
116 Are the data objects raw data or Python objects that need to be
138 Are the data objects raw data or Python objects that need to be
117 formatted before display? [default: False]
139 formatted before display? [default: False]
118 metadata : dict (optional)
140 metadata : dict (optional)
119 Metadata to be associated with the specific mimetype output.
141 Metadata to be associated with the specific mimetype output.
120 """
142 """
121
143
122 _display_mimetype('text/markdown', objs, **kwargs)
144 _display_mimetype('text/markdown', objs, **kwargs)
123
145
124
146
125 def display_svg(*objs, **kwargs):
147 def display_svg(*objs, **kwargs):
126 """Display the SVG representation of an object.
148 """Display the SVG representation of an object.
127
149
128 Parameters
150 Parameters
129 ----------
151 ----------
130 *objs : object
152 *objs : object
131 The Python objects to display, or if raw=True raw svg data to
153 The Python objects to display, or if raw=True raw svg data to
132 display.
154 display.
133 raw : bool
155 raw : bool
134 Are the data objects raw data or Python objects that need to be
156 Are the data objects raw data or Python objects that need to be
135 formatted before display? [default: False]
157 formatted before display? [default: False]
136 metadata : dict (optional)
158 metadata : dict (optional)
137 Metadata to be associated with the specific mimetype output.
159 Metadata to be associated with the specific mimetype output.
138 """
160 """
139 _display_mimetype('image/svg+xml', objs, **kwargs)
161 _display_mimetype('image/svg+xml', objs, **kwargs)
140
162
141
163
142 def display_png(*objs, **kwargs):
164 def display_png(*objs, **kwargs):
143 """Display the PNG representation of an object.
165 """Display the PNG representation of an object.
144
166
145 Parameters
167 Parameters
146 ----------
168 ----------
147 *objs : object
169 *objs : object
148 The Python objects to display, or if raw=True raw png data to
170 The Python objects to display, or if raw=True raw png data to
149 display.
171 display.
150 raw : bool
172 raw : bool
151 Are the data objects raw data or Python objects that need to be
173 Are the data objects raw data or Python objects that need to be
152 formatted before display? [default: False]
174 formatted before display? [default: False]
153 metadata : dict (optional)
175 metadata : dict (optional)
154 Metadata to be associated with the specific mimetype output.
176 Metadata to be associated with the specific mimetype output.
155 """
177 """
156 _display_mimetype('image/png', objs, **kwargs)
178 _display_mimetype('image/png', objs, **kwargs)
157
179
158
180
159 def display_jpeg(*objs, **kwargs):
181 def display_jpeg(*objs, **kwargs):
160 """Display the JPEG representation of an object.
182 """Display the JPEG representation of an object.
161
183
162 Parameters
184 Parameters
163 ----------
185 ----------
164 *objs : object
186 *objs : object
165 The Python objects to display, or if raw=True raw JPEG data to
187 The Python objects to display, or if raw=True raw JPEG data to
166 display.
188 display.
167 raw : bool
189 raw : bool
168 Are the data objects raw data or Python objects that need to be
190 Are the data objects raw data or Python objects that need to be
169 formatted before display? [default: False]
191 formatted before display? [default: False]
170 metadata : dict (optional)
192 metadata : dict (optional)
171 Metadata to be associated with the specific mimetype output.
193 Metadata to be associated with the specific mimetype output.
172 """
194 """
173 _display_mimetype('image/jpeg', objs, **kwargs)
195 _display_mimetype('image/jpeg', objs, **kwargs)
174
196
175
197
176 def display_latex(*objs, **kwargs):
198 def display_latex(*objs, **kwargs):
177 """Display the LaTeX representation of an object.
199 """Display the LaTeX representation of an object.
178
200
179 Parameters
201 Parameters
180 ----------
202 ----------
181 *objs : object
203 *objs : object
182 The Python objects to display, or if raw=True raw latex data to
204 The Python objects to display, or if raw=True raw latex data to
183 display.
205 display.
184 raw : bool
206 raw : bool
185 Are the data objects raw data or Python objects that need to be
207 Are the data objects raw data or Python objects that need to be
186 formatted before display? [default: False]
208 formatted before display? [default: False]
187 metadata : dict (optional)
209 metadata : dict (optional)
188 Metadata to be associated with the specific mimetype output.
210 Metadata to be associated with the specific mimetype output.
189 """
211 """
190 _display_mimetype('text/latex', objs, **kwargs)
212 _display_mimetype('text/latex', objs, **kwargs)
191
213
192
214
193 def display_json(*objs, **kwargs):
215 def display_json(*objs, **kwargs):
194 """Display the JSON representation of an object.
216 """Display the JSON representation of an object.
195
217
196 Note that not many frontends support displaying JSON.
218 Note that not many frontends support displaying JSON.
197
219
198 Parameters
220 Parameters
199 ----------
221 ----------
200 *objs : object
222 *objs : object
201 The Python objects to display, or if raw=True raw json data to
223 The Python objects to display, or if raw=True raw json data to
202 display.
224 display.
203 raw : bool
225 raw : bool
204 Are the data objects raw data or Python objects that need to be
226 Are the data objects raw data or Python objects that need to be
205 formatted before display? [default: False]
227 formatted before display? [default: False]
206 metadata : dict (optional)
228 metadata : dict (optional)
207 Metadata to be associated with the specific mimetype output.
229 Metadata to be associated with the specific mimetype output.
208 """
230 """
209 _display_mimetype('application/json', objs, **kwargs)
231 _display_mimetype('application/json', objs, **kwargs)
210
232
211
233
212 def display_javascript(*objs, **kwargs):
234 def display_javascript(*objs, **kwargs):
213 """Display the Javascript representation of an object.
235 """Display the Javascript representation of an object.
214
236
215 Parameters
237 Parameters
216 ----------
238 ----------
217 *objs : object
239 *objs : object
218 The Python objects to display, or if raw=True raw javascript data to
240 The Python objects to display, or if raw=True raw javascript data to
219 display.
241 display.
220 raw : bool
242 raw : bool
221 Are the data objects raw data or Python objects that need to be
243 Are the data objects raw data or Python objects that need to be
222 formatted before display? [default: False]
244 formatted before display? [default: False]
223 metadata : dict (optional)
245 metadata : dict (optional)
224 Metadata to be associated with the specific mimetype output.
246 Metadata to be associated with the specific mimetype output.
225 """
247 """
226 _display_mimetype('application/javascript', objs, **kwargs)
248 _display_mimetype('application/javascript', objs, **kwargs)
227
249
228
250
229 def display_pdf(*objs, **kwargs):
251 def display_pdf(*objs, **kwargs):
230 """Display the PDF representation of an object.
252 """Display the PDF representation of an object.
231
253
232 Parameters
254 Parameters
233 ----------
255 ----------
234 *objs : object
256 *objs : object
235 The Python objects to display, or if raw=True raw javascript data to
257 The Python objects to display, or if raw=True raw javascript data to
236 display.
258 display.
237 raw : bool
259 raw : bool
238 Are the data objects raw data or Python objects that need to be
260 Are the data objects raw data or Python objects that need to be
239 formatted before display? [default: False]
261 formatted before display? [default: False]
240 metadata : dict (optional)
262 metadata : dict (optional)
241 Metadata to be associated with the specific mimetype output.
263 Metadata to be associated with the specific mimetype output.
242 """
264 """
243 _display_mimetype('application/pdf', objs, **kwargs)
265 _display_mimetype('application/pdf', objs, **kwargs)
244
266
245
267
246 #-----------------------------------------------------------------------------
268 #-----------------------------------------------------------------------------
247 # Smart classes
269 # Smart classes
248 #-----------------------------------------------------------------------------
270 #-----------------------------------------------------------------------------
249
271
250
272
251 class DisplayObject(object):
273 class DisplayObject(object):
252 """An object that wraps data to be displayed."""
274 """An object that wraps data to be displayed."""
253
275
254 _read_flags = 'r'
276 _read_flags = 'r'
255 _show_mem_addr = False
277 _show_mem_addr = False
256 metadata = None
278 metadata = None
257
279
258 def __init__(self, data=None, url=None, filename=None, metadata=None):
280 def __init__(self, data=None, url=None, filename=None, metadata=None):
259 """Create a display object given raw data.
281 """Create a display object given raw data.
260
282
261 When this object is returned by an expression or passed to the
283 When this object is returned by an expression or passed to the
262 display function, it will result in the data being displayed
284 display function, it will result in the data being displayed
263 in the frontend. The MIME type of the data should match the
285 in the frontend. The MIME type of the data should match the
264 subclasses used, so the Png subclass should be used for 'image/png'
286 subclasses used, so the Png subclass should be used for 'image/png'
265 data. If the data is a URL, the data will first be downloaded
287 data. If the data is a URL, the data will first be downloaded
266 and then displayed. If
288 and then displayed. If
267
289
268 Parameters
290 Parameters
269 ----------
291 ----------
270 data : unicode, str or bytes
292 data : unicode, str or bytes
271 The raw data or a URL or file to load the data from
293 The raw data or a URL or file to load the data from
272 url : unicode
294 url : unicode
273 A URL to download the data from.
295 A URL to download the data from.
274 filename : unicode
296 filename : unicode
275 Path to a local file to load the data from.
297 Path to a local file to load the data from.
276 metadata : dict
298 metadata : dict
277 Dict of metadata associated to be the object when displayed
299 Dict of metadata associated to be the object when displayed
278 """
300 """
279 if isinstance(data, (Path, PurePath)):
301 if isinstance(data, (Path, PurePath)):
280 data = str(data)
302 data = str(data)
281
303
282 if data is not None and isinstance(data, str):
304 if data is not None and isinstance(data, str):
283 if data.startswith('http') and url is None:
305 if data.startswith('http') and url is None:
284 url = data
306 url = data
285 filename = None
307 filename = None
286 data = None
308 data = None
287 elif _safe_exists(data) and filename is None:
309 elif _safe_exists(data) and filename is None:
288 url = None
310 url = None
289 filename = data
311 filename = data
290 data = None
312 data = None
291
313
292 self.data = data
314 self.data = data
293 self.url = url
315 self.url = url
294 self.filename = filename
316 self.filename = filename
295
317
296 if metadata is not None:
318 if metadata is not None:
297 self.metadata = metadata
319 self.metadata = metadata
298 elif self.metadata is None:
320 elif self.metadata is None:
299 self.metadata = {}
321 self.metadata = {}
300
322
301 self.reload()
323 self.reload()
302 self._check_data()
324 self._check_data()
303
325
304 def __repr__(self):
326 def __repr__(self):
305 if not self._show_mem_addr:
327 if not self._show_mem_addr:
306 cls = self.__class__
328 cls = self.__class__
307 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
329 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
308 else:
330 else:
309 r = super(DisplayObject, self).__repr__()
331 r = super(DisplayObject, self).__repr__()
310 return r
332 return r
311
333
312 def _check_data(self):
334 def _check_data(self):
313 """Override in subclasses if there's something to check."""
335 """Override in subclasses if there's something to check."""
314 pass
336 pass
315
337
316 def _data_and_metadata(self):
338 def _data_and_metadata(self):
317 """shortcut for returning metadata with shape information, if defined"""
339 """shortcut for returning metadata with shape information, if defined"""
318 if self.metadata:
340 if self.metadata:
319 return self.data, deepcopy(self.metadata)
341 return self.data, deepcopy(self.metadata)
320 else:
342 else:
321 return self.data
343 return self.data
322
344
323 def reload(self):
345 def reload(self):
324 """Reload the raw data from file or URL."""
346 """Reload the raw data from file or URL."""
325 if self.filename is not None:
347 if self.filename is not None:
326 with open(self.filename, self._read_flags) as f:
348 with open(self.filename, self._read_flags) as f:
327 self.data = f.read()
349 self.data = f.read()
328 elif self.url is not None:
350 elif self.url is not None:
329 try:
351 try:
330 # Deferred import
352 # Deferred import
331 from urllib.request import urlopen
353 from urllib.request import urlopen
332 response = urlopen(self.url)
354 response = urlopen(self.url)
333 self.data = response.read()
355 self.data = response.read()
334 # extract encoding from header, if there is one:
356 # extract encoding from header, if there is one:
335 encoding = None
357 encoding = None
336 for sub in response.headers['content-type'].split(';'):
358 for sub in response.headers['content-type'].split(';'):
337 sub = sub.strip()
359 sub = sub.strip()
338 if sub.startswith('charset'):
360 if sub.startswith('charset'):
339 encoding = sub.split('=')[-1].strip()
361 encoding = sub.split('=')[-1].strip()
340 break
362 break
341 # decode data, if an encoding was specified
363 # decode data, if an encoding was specified
342 if encoding:
364 if encoding:
343 self.data = self.data.decode(encoding, 'replace')
365 self.data = self.data.decode(encoding, 'replace')
344 except:
366 except:
345 self.data = None
367 self.data = None
346
368
347 class TextDisplayObject(DisplayObject):
369 class TextDisplayObject(DisplayObject):
348 """Validate that display data is text"""
370 """Validate that display data is text"""
349 def _check_data(self):
371 def _check_data(self):
350 if self.data is not None and not isinstance(self.data, str):
372 if self.data is not None and not isinstance(self.data, str):
351 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
373 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
352
374
353 class Pretty(TextDisplayObject):
375 class Pretty(TextDisplayObject):
354
376
355 def _repr_pretty_(self, pp, cycle):
377 def _repr_pretty_(self, pp, cycle):
356 return pp.text(self.data)
378 return pp.text(self.data)
357
379
358
380
359 class HTML(TextDisplayObject):
381 class HTML(TextDisplayObject):
360
382
361 def __init__(self, data=None, url=None, filename=None, metadata=None):
383 def __init__(self, data=None, url=None, filename=None, metadata=None):
362 def warn():
384 def warn():
363 if not data:
385 if not data:
364 return False
386 return False
365
387
366 #
388 #
367 # Avoid calling lower() on the entire data, because it could be a
389 # Avoid calling lower() on the entire data, because it could be a
368 # long string and we're only interested in its beginning and end.
390 # long string and we're only interested in its beginning and end.
369 #
391 #
370 prefix = data[:10].lower()
392 prefix = data[:10].lower()
371 suffix = data[-10:].lower()
393 suffix = data[-10:].lower()
372 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
394 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
373
395
374 if warn():
396 if warn():
375 warnings.warn("Consider using IPython.display.IFrame instead")
397 warnings.warn("Consider using IPython.display.IFrame instead")
376 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
398 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
377
399
378 def _repr_html_(self):
400 def _repr_html_(self):
379 return self._data_and_metadata()
401 return self._data_and_metadata()
380
402
381 def __html__(self):
403 def __html__(self):
382 """
404 """
383 This method exists to inform other HTML-using modules (e.g. Markupsafe,
405 This method exists to inform other HTML-using modules (e.g. Markupsafe,
384 htmltag, etc) that this object is HTML and does not need things like
406 htmltag, etc) that this object is HTML and does not need things like
385 special characters (<>&) escaped.
407 special characters (<>&) escaped.
386 """
408 """
387 return self._repr_html_()
409 return self._repr_html_()
388
410
389
411
390 class Markdown(TextDisplayObject):
412 class Markdown(TextDisplayObject):
391
413
392 def _repr_markdown_(self):
414 def _repr_markdown_(self):
393 return self._data_and_metadata()
415 return self._data_and_metadata()
394
416
395
417
396 class Math(TextDisplayObject):
418 class Math(TextDisplayObject):
397
419
398 def _repr_latex_(self):
420 def _repr_latex_(self):
399 s = r"$\displaystyle %s$" % self.data.strip('$')
421 s = r"$\displaystyle %s$" % self.data.strip('$')
400 if self.metadata:
422 if self.metadata:
401 return s, deepcopy(self.metadata)
423 return s, deepcopy(self.metadata)
402 else:
424 else:
403 return s
425 return s
404
426
405
427
406 class Latex(TextDisplayObject):
428 class Latex(TextDisplayObject):
407
429
408 def _repr_latex_(self):
430 def _repr_latex_(self):
409 return self._data_and_metadata()
431 return self._data_and_metadata()
410
432
411
433
412 class SVG(DisplayObject):
434 class SVG(DisplayObject):
413
435
414 _read_flags = 'rb'
436 _read_flags = 'rb'
415 # wrap data in a property, which extracts the <svg> tag, discarding
437 # wrap data in a property, which extracts the <svg> tag, discarding
416 # document headers
438 # document headers
417 _data = None
439 _data = None
418
440
419 @property
441 @property
420 def data(self):
442 def data(self):
421 return self._data
443 return self._data
422
444
423 @data.setter
445 @data.setter
424 def data(self, svg):
446 def data(self, svg):
425 if svg is None:
447 if svg is None:
426 self._data = None
448 self._data = None
427 return
449 return
428 # parse into dom object
450 # parse into dom object
429 from xml.dom import minidom
451 from xml.dom import minidom
430 x = minidom.parseString(svg)
452 x = minidom.parseString(svg)
431 # get svg tag (should be 1)
453 # get svg tag (should be 1)
432 found_svg = x.getElementsByTagName('svg')
454 found_svg = x.getElementsByTagName('svg')
433 if found_svg:
455 if found_svg:
434 svg = found_svg[0].toxml()
456 svg = found_svg[0].toxml()
435 else:
457 else:
436 # fallback on the input, trust the user
458 # fallback on the input, trust the user
437 # but this is probably an error.
459 # but this is probably an error.
438 pass
460 pass
439 svg = cast_unicode(svg)
461 svg = cast_unicode(svg)
440 self._data = svg
462 self._data = svg
441
463
442 def _repr_svg_(self):
464 def _repr_svg_(self):
443 return self._data_and_metadata()
465 return self._data_and_metadata()
444
466
445 class ProgressBar(DisplayObject):
467 class ProgressBar(DisplayObject):
446 """Progressbar supports displaying a progressbar like element
468 """Progressbar supports displaying a progressbar like element
447 """
469 """
448 def __init__(self, total):
470 def __init__(self, total):
449 """Creates a new progressbar
471 """Creates a new progressbar
450
472
451 Parameters
473 Parameters
452 ----------
474 ----------
453 total : int
475 total : int
454 maximum size of the progressbar
476 maximum size of the progressbar
455 """
477 """
456 self.total = total
478 self.total = total
457 self._progress = 0
479 self._progress = 0
458 self.html_width = '60ex'
480 self.html_width = '60ex'
459 self.text_width = 60
481 self.text_width = 60
460 self._display_id = hexlify(os.urandom(8)).decode('ascii')
482 self._display_id = hexlify(os.urandom(8)).decode('ascii')
461
483
462 def __repr__(self):
484 def __repr__(self):
463 fraction = self.progress / self.total
485 fraction = self.progress / self.total
464 filled = '=' * int(fraction * self.text_width)
486 filled = '=' * int(fraction * self.text_width)
465 rest = ' ' * (self.text_width - len(filled))
487 rest = ' ' * (self.text_width - len(filled))
466 return '[{}{}] {}/{}'.format(
488 return '[{}{}] {}/{}'.format(
467 filled, rest,
489 filled, rest,
468 self.progress, self.total,
490 self.progress, self.total,
469 )
491 )
470
492
471 def _repr_html_(self):
493 def _repr_html_(self):
472 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
494 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
473 self.html_width, self.total, self.progress)
495 self.html_width, self.total, self.progress)
474
496
475 def display(self):
497 def display(self):
476 display(self, display_id=self._display_id)
498 display(self, display_id=self._display_id)
477
499
478 def update(self):
500 def update(self):
479 display(self, display_id=self._display_id, update=True)
501 display(self, display_id=self._display_id, update=True)
480
502
481 @property
503 @property
482 def progress(self):
504 def progress(self):
483 return self._progress
505 return self._progress
484
506
485 @progress.setter
507 @progress.setter
486 def progress(self, value):
508 def progress(self, value):
487 self._progress = value
509 self._progress = value
488 self.update()
510 self.update()
489
511
490 def __iter__(self):
512 def __iter__(self):
491 self.display()
513 self.display()
492 self._progress = -1 # First iteration is 0
514 self._progress = -1 # First iteration is 0
493 return self
515 return self
494
516
495 def __next__(self):
517 def __next__(self):
496 """Returns current value and increments display by one."""
518 """Returns current value and increments display by one."""
497 self.progress += 1
519 self.progress += 1
498 if self.progress < self.total:
520 if self.progress < self.total:
499 return self.progress
521 return self.progress
500 else:
522 else:
501 raise StopIteration()
523 raise StopIteration()
502
524
503 class JSON(DisplayObject):
525 class JSON(DisplayObject):
504 """JSON expects a JSON-able dict or list
526 """JSON expects a JSON-able dict or list
505
527
506 not an already-serialized JSON string.
528 not an already-serialized JSON string.
507
529
508 Scalar types (None, number, string) are not allowed, only dict or list containers.
530 Scalar types (None, number, string) are not allowed, only dict or list containers.
509 """
531 """
510 # wrap data in a property, which warns about passing already-serialized JSON
532 # wrap data in a property, which warns about passing already-serialized JSON
511 _data = None
533 _data = None
512 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
534 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
513 """Create a JSON display object given raw data.
535 """Create a JSON display object given raw data.
514
536
515 Parameters
537 Parameters
516 ----------
538 ----------
517 data : dict or list
539 data : dict or list
518 JSON data to display. Not an already-serialized JSON string.
540 JSON data to display. Not an already-serialized JSON string.
519 Scalar types (None, number, string) are not allowed, only dict
541 Scalar types (None, number, string) are not allowed, only dict
520 or list containers.
542 or list containers.
521 url : unicode
543 url : unicode
522 A URL to download the data from.
544 A URL to download the data from.
523 filename : unicode
545 filename : unicode
524 Path to a local file to load the data from.
546 Path to a local file to load the data from.
525 expanded : boolean
547 expanded : boolean
526 Metadata to control whether a JSON display component is expanded.
548 Metadata to control whether a JSON display component is expanded.
527 metadata: dict
549 metadata: dict
528 Specify extra metadata to attach to the json display object.
550 Specify extra metadata to attach to the json display object.
529 root : str
551 root : str
530 The name of the root element of the JSON tree
552 The name of the root element of the JSON tree
531 """
553 """
532 self.metadata = {
554 self.metadata = {
533 'expanded': expanded,
555 'expanded': expanded,
534 'root': root,
556 'root': root,
535 }
557 }
536 if metadata:
558 if metadata:
537 self.metadata.update(metadata)
559 self.metadata.update(metadata)
538 if kwargs:
560 if kwargs:
539 self.metadata.update(kwargs)
561 self.metadata.update(kwargs)
540 super(JSON, self).__init__(data=data, url=url, filename=filename)
562 super(JSON, self).__init__(data=data, url=url, filename=filename)
541
563
542 def _check_data(self):
564 def _check_data(self):
543 if self.data is not None and not isinstance(self.data, (dict, list)):
565 if self.data is not None and not isinstance(self.data, (dict, list)):
544 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
566 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
545
567
546 @property
568 @property
547 def data(self):
569 def data(self):
548 return self._data
570 return self._data
549
571
550 @data.setter
572 @data.setter
551 def data(self, data):
573 def data(self, data):
552 if isinstance(data, (Path, PurePath)):
574 if isinstance(data, (Path, PurePath)):
553 data = str(data)
575 data = str(data)
554
576
555 if isinstance(data, str):
577 if isinstance(data, str):
556 if getattr(self, 'filename', None) is None:
578 if getattr(self, 'filename', None) is None:
557 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
579 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
558 data = json.loads(data)
580 data = json.loads(data)
559 self._data = data
581 self._data = data
560
582
561 def _data_and_metadata(self):
583 def _data_and_metadata(self):
562 return self.data, self.metadata
584 return self.data, self.metadata
563
585
564 def _repr_json_(self):
586 def _repr_json_(self):
565 return self._data_and_metadata()
587 return self._data_and_metadata()
566
588
567 _css_t = """var link = document.createElement("link");
589 _css_t = """var link = document.createElement("link");
568 link.ref = "stylesheet";
590 link.ref = "stylesheet";
569 link.type = "text/css";
591 link.type = "text/css";
570 link.href = "%s";
592 link.href = "%s";
571 document.head.appendChild(link);
593 document.head.appendChild(link);
572 """
594 """
573
595
574 _lib_t1 = """new Promise(function(resolve, reject) {
596 _lib_t1 = """new Promise(function(resolve, reject) {
575 var script = document.createElement("script");
597 var script = document.createElement("script");
576 script.onload = resolve;
598 script.onload = resolve;
577 script.onerror = reject;
599 script.onerror = reject;
578 script.src = "%s";
600 script.src = "%s";
579 document.head.appendChild(script);
601 document.head.appendChild(script);
580 }).then(() => {
602 }).then(() => {
581 """
603 """
582
604
583 _lib_t2 = """
605 _lib_t2 = """
584 });"""
606 });"""
585
607
586 class GeoJSON(JSON):
608 class GeoJSON(JSON):
587 """GeoJSON expects JSON-able dict
609 """GeoJSON expects JSON-able dict
588
610
589 not an already-serialized JSON string.
611 not an already-serialized JSON string.
590
612
591 Scalar types (None, number, string) are not allowed, only dict containers.
613 Scalar types (None, number, string) are not allowed, only dict containers.
592 """
614 """
593
615
594 def __init__(self, *args, **kwargs):
616 def __init__(self, *args, **kwargs):
595 """Create a GeoJSON display object given raw data.
617 """Create a GeoJSON display object given raw data.
596
618
597 Parameters
619 Parameters
598 ----------
620 ----------
599 data : dict or list
621 data : dict or list
600 VegaLite data. Not an already-serialized JSON string.
622 VegaLite data. Not an already-serialized JSON string.
601 Scalar types (None, number, string) are not allowed, only dict
623 Scalar types (None, number, string) are not allowed, only dict
602 or list containers.
624 or list containers.
603 url_template : string
625 url_template : string
604 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
626 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
605 layer_options : dict
627 layer_options : dict
606 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
628 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
607 url : unicode
629 url : unicode
608 A URL to download the data from.
630 A URL to download the data from.
609 filename : unicode
631 filename : unicode
610 Path to a local file to load the data from.
632 Path to a local file to load the data from.
611 metadata: dict
633 metadata: dict
612 Specify extra metadata to attach to the json display object.
634 Specify extra metadata to attach to the json display object.
613
635
614 Examples
636 Examples
615 --------
637 --------
616
638
617 The following will display an interactive map of Mars with a point of
639 The following will display an interactive map of Mars with a point of
618 interest on frontend that do support GeoJSON display.
640 interest on frontend that do support GeoJSON display.
619
641
620 >>> from IPython.display import GeoJSON
642 >>> from IPython.display import GeoJSON
621
643
622 >>> GeoJSON(data={
644 >>> GeoJSON(data={
623 ... "type": "Feature",
645 ... "type": "Feature",
624 ... "geometry": {
646 ... "geometry": {
625 ... "type": "Point",
647 ... "type": "Point",
626 ... "coordinates": [-81.327, 296.038]
648 ... "coordinates": [-81.327, 296.038]
627 ... }
649 ... }
628 ... },
650 ... },
629 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
651 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
630 ... layer_options={
652 ... layer_options={
631 ... "basemap_id": "celestia_mars-shaded-16k_global",
653 ... "basemap_id": "celestia_mars-shaded-16k_global",
632 ... "attribution" : "Celestia/praesepe",
654 ... "attribution" : "Celestia/praesepe",
633 ... "minZoom" : 0,
655 ... "minZoom" : 0,
634 ... "maxZoom" : 18,
656 ... "maxZoom" : 18,
635 ... })
657 ... })
636 <IPython.core.display.GeoJSON object>
658 <IPython.core.display.GeoJSON object>
637
659
638 In the terminal IPython, you will only see the text representation of
660 In the terminal IPython, you will only see the text representation of
639 the GeoJSON object.
661 the GeoJSON object.
640
662
641 """
663 """
642
664
643 super(GeoJSON, self).__init__(*args, **kwargs)
665 super(GeoJSON, self).__init__(*args, **kwargs)
644
666
645
667
646 def _ipython_display_(self):
668 def _ipython_display_(self):
647 bundle = {
669 bundle = {
648 'application/geo+json': self.data,
670 'application/geo+json': self.data,
649 'text/plain': '<IPython.display.GeoJSON object>'
671 'text/plain': '<IPython.display.GeoJSON object>'
650 }
672 }
651 metadata = {
673 metadata = {
652 'application/geo+json': self.metadata
674 'application/geo+json': self.metadata
653 }
675 }
654 display(bundle, metadata=metadata, raw=True)
676 display(bundle, metadata=metadata, raw=True)
655
677
656 class Javascript(TextDisplayObject):
678 class Javascript(TextDisplayObject):
657
679
658 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
680 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
659 """Create a Javascript display object given raw data.
681 """Create a Javascript display object given raw data.
660
682
661 When this object is returned by an expression or passed to the
683 When this object is returned by an expression or passed to the
662 display function, it will result in the data being displayed
684 display function, it will result in the data being displayed
663 in the frontend. If the data is a URL, the data will first be
685 in the frontend. If the data is a URL, the data will first be
664 downloaded and then displayed.
686 downloaded and then displayed.
665
687
666 In the Notebook, the containing element will be available as `element`,
688 In the Notebook, the containing element will be available as `element`,
667 and jQuery will be available. Content appended to `element` will be
689 and jQuery will be available. Content appended to `element` will be
668 visible in the output area.
690 visible in the output area.
669
691
670 Parameters
692 Parameters
671 ----------
693 ----------
672 data : unicode, str or bytes
694 data : unicode, str or bytes
673 The Javascript source code or a URL to download it from.
695 The Javascript source code or a URL to download it from.
674 url : unicode
696 url : unicode
675 A URL to download the data from.
697 A URL to download the data from.
676 filename : unicode
698 filename : unicode
677 Path to a local file to load the data from.
699 Path to a local file to load the data from.
678 lib : list or str
700 lib : list or str
679 A sequence of Javascript library URLs to load asynchronously before
701 A sequence of Javascript library URLs to load asynchronously before
680 running the source code. The full URLs of the libraries should
702 running the source code. The full URLs of the libraries should
681 be given. A single Javascript library URL can also be given as a
703 be given. A single Javascript library URL can also be given as a
682 string.
704 string.
683 css: : list or str
705 css: : list or str
684 A sequence of css files to load before running the source code.
706 A sequence of css files to load before running the source code.
685 The full URLs of the css files should be given. A single css URL
707 The full URLs of the css files should be given. A single css URL
686 can also be given as a string.
708 can also be given as a string.
687 """
709 """
688 if isinstance(lib, str):
710 if isinstance(lib, str):
689 lib = [lib]
711 lib = [lib]
690 elif lib is None:
712 elif lib is None:
691 lib = []
713 lib = []
692 if isinstance(css, str):
714 if isinstance(css, str):
693 css = [css]
715 css = [css]
694 elif css is None:
716 elif css is None:
695 css = []
717 css = []
696 if not isinstance(lib, (list,tuple)):
718 if not isinstance(lib, (list,tuple)):
697 raise TypeError('expected sequence, got: %r' % lib)
719 raise TypeError('expected sequence, got: %r' % lib)
698 if not isinstance(css, (list,tuple)):
720 if not isinstance(css, (list,tuple)):
699 raise TypeError('expected sequence, got: %r' % css)
721 raise TypeError('expected sequence, got: %r' % css)
700 self.lib = lib
722 self.lib = lib
701 self.css = css
723 self.css = css
702 super(Javascript, self).__init__(data=data, url=url, filename=filename)
724 super(Javascript, self).__init__(data=data, url=url, filename=filename)
703
725
704 def _repr_javascript_(self):
726 def _repr_javascript_(self):
705 r = ''
727 r = ''
706 for c in self.css:
728 for c in self.css:
707 r += _css_t % c
729 r += _css_t % c
708 for l in self.lib:
730 for l in self.lib:
709 r += _lib_t1 % l
731 r += _lib_t1 % l
710 r += self.data
732 r += self.data
711 r += _lib_t2*len(self.lib)
733 r += _lib_t2*len(self.lib)
712 return r
734 return r
713
735
714 # constants for identifying png/jpeg data
736 # constants for identifying png/jpeg data
715 _PNG = b'\x89PNG\r\n\x1a\n'
737 _PNG = b'\x89PNG\r\n\x1a\n'
716 _JPEG = b'\xff\xd8'
738 _JPEG = b'\xff\xd8'
717
739
718 def _pngxy(data):
740 def _pngxy(data):
719 """read the (width, height) from a PNG header"""
741 """read the (width, height) from a PNG header"""
720 ihdr = data.index(b'IHDR')
742 ihdr = data.index(b'IHDR')
721 # next 8 bytes are width/height
743 # next 8 bytes are width/height
722 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
744 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
723
745
724 def _jpegxy(data):
746 def _jpegxy(data):
725 """read the (width, height) from a JPEG header"""
747 """read the (width, height) from a JPEG header"""
726 # adapted from http://www.64lines.com/jpeg-width-height
748 # adapted from http://www.64lines.com/jpeg-width-height
727
749
728 idx = 4
750 idx = 4
729 while True:
751 while True:
730 block_size = struct.unpack('>H', data[idx:idx+2])[0]
752 block_size = struct.unpack('>H', data[idx:idx+2])[0]
731 idx = idx + block_size
753 idx = idx + block_size
732 if data[idx:idx+2] == b'\xFF\xC0':
754 if data[idx:idx+2] == b'\xFF\xC0':
733 # found Start of Frame
755 # found Start of Frame
734 iSOF = idx
756 iSOF = idx
735 break
757 break
736 else:
758 else:
737 # read another block
759 # read another block
738 idx += 2
760 idx += 2
739
761
740 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
762 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
741 return w, h
763 return w, h
742
764
743 def _gifxy(data):
765 def _gifxy(data):
744 """read the (width, height) from a GIF header"""
766 """read the (width, height) from a GIF header"""
745 return struct.unpack('<HH', data[6:10])
767 return struct.unpack('<HH', data[6:10])
746
768
747
769
748 class Image(DisplayObject):
770 class Image(DisplayObject):
749
771
750 _read_flags = 'rb'
772 _read_flags = 'rb'
751 _FMT_JPEG = u'jpeg'
773 _FMT_JPEG = u'jpeg'
752 _FMT_PNG = u'png'
774 _FMT_PNG = u'png'
753 _FMT_GIF = u'gif'
775 _FMT_GIF = u'gif'
754 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
776 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
755 _MIMETYPES = {
777 _MIMETYPES = {
756 _FMT_PNG: 'image/png',
778 _FMT_PNG: 'image/png',
757 _FMT_JPEG: 'image/jpeg',
779 _FMT_JPEG: 'image/jpeg',
758 _FMT_GIF: 'image/gif',
780 _FMT_GIF: 'image/gif',
759 }
781 }
760
782
761 def __init__(self, data=None, url=None, filename=None, format=None,
783 def __init__(self, data=None, url=None, filename=None, format=None,
762 embed=None, width=None, height=None, retina=False,
784 embed=None, width=None, height=None, retina=False,
763 unconfined=False, metadata=None):
785 unconfined=False, metadata=None):
764 """Create a PNG/JPEG/GIF image object given raw data.
786 """Create a PNG/JPEG/GIF image object given raw data.
765
787
766 When this object is returned by an input cell or passed to the
788 When this object is returned by an input cell or passed to the
767 display function, it will result in the image being displayed
789 display function, it will result in the image being displayed
768 in the frontend.
790 in the frontend.
769
791
770 Parameters
792 Parameters
771 ----------
793 ----------
772 data : unicode, str or bytes
794 data : unicode, str or bytes
773 The raw image data or a URL or filename to load the data from.
795 The raw image data or a URL or filename to load the data from.
774 This always results in embedded image data.
796 This always results in embedded image data.
775 url : unicode
797 url : unicode
776 A URL to download the data from. If you specify `url=`,
798 A URL to download the data from. If you specify `url=`,
777 the image data will not be embedded unless you also specify `embed=True`.
799 the image data will not be embedded unless you also specify `embed=True`.
778 filename : unicode
800 filename : unicode
779 Path to a local file to load the data from.
801 Path to a local file to load the data from.
780 Images from a file are always embedded.
802 Images from a file are always embedded.
781 format : unicode
803 format : unicode
782 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
804 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
783 for format will be inferred from the filename extension.
805 for format will be inferred from the filename extension.
784 embed : bool
806 embed : bool
785 Should the image data be embedded using a data URI (True) or be
807 Should the image data be embedded using a data URI (True) or be
786 loaded using an <img> tag. Set this to True if you want the image
808 loaded using an <img> tag. Set this to True if you want the image
787 to be viewable later with no internet connection in the notebook.
809 to be viewable later with no internet connection in the notebook.
788
810
789 Default is `True`, unless the keyword argument `url` is set, then
811 Default is `True`, unless the keyword argument `url` is set, then
790 default value is `False`.
812 default value is `False`.
791
813
792 Note that QtConsole is not able to display images if `embed` is set to `False`
814 Note that QtConsole is not able to display images if `embed` is set to `False`
793 width : int
815 width : int
794 Width in pixels to which to constrain the image in html
816 Width in pixels to which to constrain the image in html
795 height : int
817 height : int
796 Height in pixels to which to constrain the image in html
818 Height in pixels to which to constrain the image in html
797 retina : bool
819 retina : bool
798 Automatically set the width and height to half of the measured
820 Automatically set the width and height to half of the measured
799 width and height.
821 width and height.
800 This only works for embedded images because it reads the width/height
822 This only works for embedded images because it reads the width/height
801 from image data.
823 from image data.
802 For non-embedded images, you can just set the desired display width
824 For non-embedded images, you can just set the desired display width
803 and height directly.
825 and height directly.
804 unconfined: bool
826 unconfined: bool
805 Set unconfined=True to disable max-width confinement of the image.
827 Set unconfined=True to disable max-width confinement of the image.
806 metadata: dict
828 metadata: dict
807 Specify extra metadata to attach to the image.
829 Specify extra metadata to attach to the image.
808
830
809 Examples
831 Examples
810 --------
832 --------
811 # embedded image data, works in qtconsole and notebook
833 # embedded image data, works in qtconsole and notebook
812 # when passed positionally, the first arg can be any of raw image data,
834 # when passed positionally, the first arg can be any of raw image data,
813 # a URL, or a filename from which to load image data.
835 # a URL, or a filename from which to load image data.
814 # The result is always embedding image data for inline images.
836 # The result is always embedding image data for inline images.
815 Image('http://www.google.fr/images/srpr/logo3w.png')
837 Image('http://www.google.fr/images/srpr/logo3w.png')
816 Image('/path/to/image.jpg')
838 Image('/path/to/image.jpg')
817 Image(b'RAW_PNG_DATA...')
839 Image(b'RAW_PNG_DATA...')
818
840
819 # Specifying Image(url=...) does not embed the image data,
841 # Specifying Image(url=...) does not embed the image data,
820 # it only generates `<img>` tag with a link to the source.
842 # it only generates `<img>` tag with a link to the source.
821 # This will not work in the qtconsole or offline.
843 # This will not work in the qtconsole or offline.
822 Image(url='http://www.google.fr/images/srpr/logo3w.png')
844 Image(url='http://www.google.fr/images/srpr/logo3w.png')
823
845
824 """
846 """
825 if isinstance(data, (Path, PurePath)):
847 if isinstance(data, (Path, PurePath)):
826 data = str(data)
848 data = str(data)
827
849
828 if filename is not None:
850 if filename is not None:
829 ext = self._find_ext(filename)
851 ext = self._find_ext(filename)
830 elif url is not None:
852 elif url is not None:
831 ext = self._find_ext(url)
853 ext = self._find_ext(url)
832 elif data is None:
854 elif data is None:
833 raise ValueError("No image data found. Expecting filename, url, or data.")
855 raise ValueError("No image data found. Expecting filename, url, or data.")
834 elif isinstance(data, str) and (
856 elif isinstance(data, str) and (
835 data.startswith('http') or _safe_exists(data)
857 data.startswith('http') or _safe_exists(data)
836 ):
858 ):
837 ext = self._find_ext(data)
859 ext = self._find_ext(data)
838 else:
860 else:
839 ext = None
861 ext = None
840
862
841 if format is None:
863 if format is None:
842 if ext is not None:
864 if ext is not None:
843 if ext == u'jpg' or ext == u'jpeg':
865 if ext == u'jpg' or ext == u'jpeg':
844 format = self._FMT_JPEG
866 format = self._FMT_JPEG
845 elif ext == u'png':
867 elif ext == u'png':
846 format = self._FMT_PNG
868 format = self._FMT_PNG
847 elif ext == u'gif':
869 elif ext == u'gif':
848 format = self._FMT_GIF
870 format = self._FMT_GIF
849 else:
871 else:
850 format = ext.lower()
872 format = ext.lower()
851 elif isinstance(data, bytes):
873 elif isinstance(data, bytes):
852 # infer image type from image data header,
874 # infer image type from image data header,
853 # only if format has not been specified.
875 # only if format has not been specified.
854 if data[:2] == _JPEG:
876 if data[:2] == _JPEG:
855 format = self._FMT_JPEG
877 format = self._FMT_JPEG
856
878
857 # failed to detect format, default png
879 # failed to detect format, default png
858 if format is None:
880 if format is None:
859 format = self._FMT_PNG
881 format = self._FMT_PNG
860
882
861 if format.lower() == 'jpg':
883 if format.lower() == 'jpg':
862 # jpg->jpeg
884 # jpg->jpeg
863 format = self._FMT_JPEG
885 format = self._FMT_JPEG
864
886
865 self.format = format.lower()
887 self.format = format.lower()
866 self.embed = embed if embed is not None else (url is None)
888 self.embed = embed if embed is not None else (url is None)
867
889
868 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
890 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
869 raise ValueError("Cannot embed the '%s' image format" % (self.format))
891 raise ValueError("Cannot embed the '%s' image format" % (self.format))
870 if self.embed:
892 if self.embed:
871 self._mimetype = self._MIMETYPES.get(self.format)
893 self._mimetype = self._MIMETYPES.get(self.format)
872
894
873 self.width = width
895 self.width = width
874 self.height = height
896 self.height = height
875 self.retina = retina
897 self.retina = retina
876 self.unconfined = unconfined
898 self.unconfined = unconfined
877 super(Image, self).__init__(data=data, url=url, filename=filename,
899 super(Image, self).__init__(data=data, url=url, filename=filename,
878 metadata=metadata)
900 metadata=metadata)
879
901
880 if self.width is None and self.metadata.get('width', {}):
902 if self.width is None and self.metadata.get('width', {}):
881 self.width = metadata['width']
903 self.width = metadata['width']
882
904
883 if self.height is None and self.metadata.get('height', {}):
905 if self.height is None and self.metadata.get('height', {}):
884 self.height = metadata['height']
906 self.height = metadata['height']
885
907
886 if retina:
908 if retina:
887 self._retina_shape()
909 self._retina_shape()
888
910
889
911
890 def _retina_shape(self):
912 def _retina_shape(self):
891 """load pixel-doubled width and height from image data"""
913 """load pixel-doubled width and height from image data"""
892 if not self.embed:
914 if not self.embed:
893 return
915 return
894 if self.format == self._FMT_PNG:
916 if self.format == self._FMT_PNG:
895 w, h = _pngxy(self.data)
917 w, h = _pngxy(self.data)
896 elif self.format == self._FMT_JPEG:
918 elif self.format == self._FMT_JPEG:
897 w, h = _jpegxy(self.data)
919 w, h = _jpegxy(self.data)
898 elif self.format == self._FMT_GIF:
920 elif self.format == self._FMT_GIF:
899 w, h = _gifxy(self.data)
921 w, h = _gifxy(self.data)
900 else:
922 else:
901 # retina only supports png
923 # retina only supports png
902 return
924 return
903 self.width = w // 2
925 self.width = w // 2
904 self.height = h // 2
926 self.height = h // 2
905
927
906 def reload(self):
928 def reload(self):
907 """Reload the raw data from file or URL."""
929 """Reload the raw data from file or URL."""
908 if self.embed:
930 if self.embed:
909 super(Image,self).reload()
931 super(Image,self).reload()
910 if self.retina:
932 if self.retina:
911 self._retina_shape()
933 self._retina_shape()
912
934
913 def _repr_html_(self):
935 def _repr_html_(self):
914 if not self.embed:
936 if not self.embed:
915 width = height = klass = ''
937 width = height = klass = ''
916 if self.width:
938 if self.width:
917 width = ' width="%d"' % self.width
939 width = ' width="%d"' % self.width
918 if self.height:
940 if self.height:
919 height = ' height="%d"' % self.height
941 height = ' height="%d"' % self.height
920 if self.unconfined:
942 if self.unconfined:
921 klass = ' class="unconfined"'
943 klass = ' class="unconfined"'
922 return u'<img src="{url}"{width}{height}{klass}/>'.format(
944 return u'<img src="{url}"{width}{height}{klass}/>'.format(
923 url=self.url,
945 url=self.url,
924 width=width,
946 width=width,
925 height=height,
947 height=height,
926 klass=klass,
948 klass=klass,
927 )
949 )
928
950
929 def _repr_mimebundle_(self, include=None, exclude=None):
951 def _repr_mimebundle_(self, include=None, exclude=None):
930 """Return the image as a mimebundle
952 """Return the image as a mimebundle
931
953
932 Any new mimetype support should be implemented here.
954 Any new mimetype support should be implemented here.
933 """
955 """
934 if self.embed:
956 if self.embed:
935 mimetype = self._mimetype
957 mimetype = self._mimetype
936 data, metadata = self._data_and_metadata(always_both=True)
958 data, metadata = self._data_and_metadata(always_both=True)
937 if metadata:
959 if metadata:
938 metadata = {mimetype: metadata}
960 metadata = {mimetype: metadata}
939 return {mimetype: data}, metadata
961 return {mimetype: data}, metadata
940 else:
962 else:
941 return {'text/html': self._repr_html_()}
963 return {'text/html': self._repr_html_()}
942
964
943 def _data_and_metadata(self, always_both=False):
965 def _data_and_metadata(self, always_both=False):
944 """shortcut for returning metadata with shape information, if defined"""
966 """shortcut for returning metadata with shape information, if defined"""
945 try:
967 try:
946 b64_data = b2a_base64(self.data).decode('ascii')
968 b64_data = b2a_base64(self.data).decode('ascii')
947 except TypeError:
969 except TypeError:
948 raise FileNotFoundError(
970 raise FileNotFoundError(
949 "No such file or directory: '%s'" % (self.data))
971 "No such file or directory: '%s'" % (self.data))
950 md = {}
972 md = {}
951 if self.metadata:
973 if self.metadata:
952 md.update(self.metadata)
974 md.update(self.metadata)
953 if self.width:
975 if self.width:
954 md['width'] = self.width
976 md['width'] = self.width
955 if self.height:
977 if self.height:
956 md['height'] = self.height
978 md['height'] = self.height
957 if self.unconfined:
979 if self.unconfined:
958 md['unconfined'] = self.unconfined
980 md['unconfined'] = self.unconfined
959 if md or always_both:
981 if md or always_both:
960 return b64_data, md
982 return b64_data, md
961 else:
983 else:
962 return b64_data
984 return b64_data
963
985
964 def _repr_png_(self):
986 def _repr_png_(self):
965 if self.embed and self.format == self._FMT_PNG:
987 if self.embed and self.format == self._FMT_PNG:
966 return self._data_and_metadata()
988 return self._data_and_metadata()
967
989
968 def _repr_jpeg_(self):
990 def _repr_jpeg_(self):
969 if self.embed and self.format == self._FMT_JPEG:
991 if self.embed and self.format == self._FMT_JPEG:
970 return self._data_and_metadata()
992 return self._data_and_metadata()
971
993
972 def _find_ext(self, s):
994 def _find_ext(self, s):
973 base, ext = splitext(s)
995 base, ext = splitext(s)
974
996
975 if not ext:
997 if not ext:
976 return base
998 return base
977
999
978 # `splitext` includes leading period, so we skip it
1000 # `splitext` includes leading period, so we skip it
979 return ext[1:].lower()
1001 return ext[1:].lower()
980
1002
981
1003
982 class Video(DisplayObject):
1004 class Video(DisplayObject):
983
1005
984 def __init__(self, data=None, url=None, filename=None, embed=False,
1006 def __init__(self, data=None, url=None, filename=None, embed=False,
985 mimetype=None, width=None, height=None):
1007 mimetype=None, width=None, height=None):
986 """Create a video object given raw data or an URL.
1008 """Create a video object given raw data or an URL.
987
1009
988 When this object is returned by an input cell or passed to the
1010 When this object is returned by an input cell or passed to the
989 display function, it will result in the video being displayed
1011 display function, it will result in the video being displayed
990 in the frontend.
1012 in the frontend.
991
1013
992 Parameters
1014 Parameters
993 ----------
1015 ----------
994 data : unicode, str or bytes
1016 data : unicode, str or bytes
995 The raw video data or a URL or filename to load the data from.
1017 The raw video data or a URL or filename to load the data from.
996 Raw data will require passing `embed=True`.
1018 Raw data will require passing `embed=True`.
997 url : unicode
1019 url : unicode
998 A URL for the video. If you specify `url=`,
1020 A URL for the video. If you specify `url=`,
999 the image data will not be embedded.
1021 the image data will not be embedded.
1000 filename : unicode
1022 filename : unicode
1001 Path to a local file containing the video.
1023 Path to a local file containing the video.
1002 Will be interpreted as a local URL unless `embed=True`.
1024 Will be interpreted as a local URL unless `embed=True`.
1003 embed : bool
1025 embed : bool
1004 Should the video be embedded using a data URI (True) or be
1026 Should the video be embedded using a data URI (True) or be
1005 loaded using a <video> tag (False).
1027 loaded using a <video> tag (False).
1006
1028
1007 Since videos are large, embedding them should be avoided, if possible.
1029 Since videos are large, embedding them should be avoided, if possible.
1008 You must confirm embedding as your intention by passing `embed=True`.
1030 You must confirm embedding as your intention by passing `embed=True`.
1009
1031
1010 Local files can be displayed with URLs without embedding the content, via::
1032 Local files can be displayed with URLs without embedding the content, via::
1011
1033
1012 Video('./video.mp4')
1034 Video('./video.mp4')
1013
1035
1014 mimetype: unicode
1036 mimetype: unicode
1015 Specify the mimetype for embedded videos.
1037 Specify the mimetype for embedded videos.
1016 Default will be guessed from file extension, if available.
1038 Default will be guessed from file extension, if available.
1017 width : int
1039 width : int
1018 Width in pixels to which to constrain the video in HTML.
1040 Width in pixels to which to constrain the video in HTML.
1019 If not supplied, defaults to the width of the video.
1041 If not supplied, defaults to the width of the video.
1020 height : int
1042 height : int
1021 Height in pixels to which to constrain the video in html.
1043 Height in pixels to which to constrain the video in html.
1022 If not supplied, defaults to the height of the video.
1044 If not supplied, defaults to the height of the video.
1023
1045
1024 Examples
1046 Examples
1025 --------
1047 --------
1026
1048
1027 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1049 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1028 Video('path/to/video.mp4')
1050 Video('path/to/video.mp4')
1029 Video('path/to/video.mp4', embed=True)
1051 Video('path/to/video.mp4', embed=True)
1030 Video(b'raw-videodata', embed=True)
1052 Video(b'raw-videodata', embed=True)
1031 """
1053 """
1032 if isinstance(data, (Path, PurePath)):
1054 if isinstance(data, (Path, PurePath)):
1033 data = str(data)
1055 data = str(data)
1034
1056
1035 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1057 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1036 url = data
1058 url = data
1037 data = None
1059 data = None
1038 elif os.path.exists(data):
1060 elif os.path.exists(data):
1039 filename = data
1061 filename = data
1040 data = None
1062 data = None
1041
1063
1042 if data and not embed:
1064 if data and not embed:
1043 msg = ''.join([
1065 msg = ''.join([
1044 "To embed videos, you must pass embed=True ",
1066 "To embed videos, you must pass embed=True ",
1045 "(this may make your notebook files huge)\n",
1067 "(this may make your notebook files huge)\n",
1046 "Consider passing Video(url='...')",
1068 "Consider passing Video(url='...')",
1047 ])
1069 ])
1048 raise ValueError(msg)
1070 raise ValueError(msg)
1049
1071
1050 self.mimetype = mimetype
1072 self.mimetype = mimetype
1051 self.embed = embed
1073 self.embed = embed
1052 self.width = width
1074 self.width = width
1053 self.height = height
1075 self.height = height
1054 super(Video, self).__init__(data=data, url=url, filename=filename)
1076 super(Video, self).__init__(data=data, url=url, filename=filename)
1055
1077
1056 def _repr_html_(self):
1078 def _repr_html_(self):
1057 width = height = ''
1079 width = height = ''
1058 if self.width:
1080 if self.width:
1059 width = ' width="%d"' % self.width
1081 width = ' width="%d"' % self.width
1060 if self.height:
1082 if self.height:
1061 height = ' height="%d"' % self.height
1083 height = ' height="%d"' % self.height
1062
1084
1063 # External URLs and potentially local files are not embedded into the
1085 # External URLs and potentially local files are not embedded into the
1064 # notebook output.
1086 # notebook output.
1065 if not self.embed:
1087 if not self.embed:
1066 url = self.url if self.url is not None else self.filename
1088 url = self.url if self.url is not None else self.filename
1067 output = """<video src="{0}" controls {1} {2}>
1089 output = """<video src="{0}" controls {1} {2}>
1068 Your browser does not support the <code>video</code> element.
1090 Your browser does not support the <code>video</code> element.
1069 </video>""".format(url, width, height)
1091 </video>""".format(url, width, height)
1070 return output
1092 return output
1071
1093
1072 # Embedded videos are base64-encoded.
1094 # Embedded videos are base64-encoded.
1073 mimetype = self.mimetype
1095 mimetype = self.mimetype
1074 if self.filename is not None:
1096 if self.filename is not None:
1075 if not mimetype:
1097 if not mimetype:
1076 mimetype, _ = mimetypes.guess_type(self.filename)
1098 mimetype, _ = mimetypes.guess_type(self.filename)
1077
1099
1078 with open(self.filename, 'rb') as f:
1100 with open(self.filename, 'rb') as f:
1079 video = f.read()
1101 video = f.read()
1080 else:
1102 else:
1081 video = self.data
1103 video = self.data
1082 if isinstance(video, str):
1104 if isinstance(video, str):
1083 # unicode input is already b64-encoded
1105 # unicode input is already b64-encoded
1084 b64_video = video
1106 b64_video = video
1085 else:
1107 else:
1086 b64_video = b2a_base64(video).decode('ascii').rstrip()
1108 b64_video = b2a_base64(video).decode('ascii').rstrip()
1087
1109
1088 output = """<video controls {0} {1}>
1110 output = """<video controls {0} {1}>
1089 <source src="data:{2};base64,{3}" type="{2}">
1111 <source src="data:{2};base64,{3}" type="{2}">
1090 Your browser does not support the video tag.
1112 Your browser does not support the video tag.
1091 </video>""".format(width, height, mimetype, b64_video)
1113 </video>""".format(width, height, mimetype, b64_video)
1092 return output
1114 return output
1093
1115
1094 def reload(self):
1116 def reload(self):
1095 # TODO
1117 # TODO
1096 pass
1118 pass
1097
1119
1098
1120
1099 @skip_doctest
1121 @skip_doctest
1100 def set_matplotlib_formats(*formats, **kwargs):
1122 def set_matplotlib_formats(*formats, **kwargs):
1101 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1123 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1102
1124
1103 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1125 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1104
1126
1105 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1127 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1106
1128
1107 To set this in your config files use the following::
1129 To set this in your config files use the following::
1108
1130
1109 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1131 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1110 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1132 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1111
1133
1112 Parameters
1134 Parameters
1113 ----------
1135 ----------
1114 *formats : strs
1136 *formats : strs
1115 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1137 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1116 **kwargs :
1138 **kwargs :
1117 Keyword args will be relayed to ``figure.canvas.print_figure``.
1139 Keyword args will be relayed to ``figure.canvas.print_figure``.
1118 """
1140 """
1119 from IPython.core.interactiveshell import InteractiveShell
1141 from IPython.core.interactiveshell import InteractiveShell
1120 from IPython.core.pylabtools import select_figure_formats
1142 from IPython.core.pylabtools import select_figure_formats
1121 # build kwargs, starting with InlineBackend config
1143 # build kwargs, starting with InlineBackend config
1122 kw = {}
1144 kw = {}
1123 from ipykernel.pylab.config import InlineBackend
1145 from ipykernel.pylab.config import InlineBackend
1124 cfg = InlineBackend.instance()
1146 cfg = InlineBackend.instance()
1125 kw.update(cfg.print_figure_kwargs)
1147 kw.update(cfg.print_figure_kwargs)
1126 kw.update(**kwargs)
1148 kw.update(**kwargs)
1127 shell = InteractiveShell.instance()
1149 shell = InteractiveShell.instance()
1128 select_figure_formats(shell, formats, **kw)
1150 select_figure_formats(shell, formats, **kw)
1129
1151
1130 @skip_doctest
1152 @skip_doctest
1131 def set_matplotlib_close(close=True):
1153 def set_matplotlib_close(close=True):
1132 """Set whether the inline backend closes all figures automatically or not.
1154 """Set whether the inline backend closes all figures automatically or not.
1133
1155
1134 By default, the inline backend used in the IPython Notebook will close all
1156 By default, the inline backend used in the IPython Notebook will close all
1135 matplotlib figures automatically after each cell is run. This means that
1157 matplotlib figures automatically after each cell is run. This means that
1136 plots in different cells won't interfere. Sometimes, you may want to make
1158 plots in different cells won't interfere. Sometimes, you may want to make
1137 a plot in one cell and then refine it in later cells. This can be accomplished
1159 a plot in one cell and then refine it in later cells. This can be accomplished
1138 by::
1160 by::
1139
1161
1140 In [1]: set_matplotlib_close(False)
1162 In [1]: set_matplotlib_close(False)
1141
1163
1142 To set this in your config files use the following::
1164 To set this in your config files use the following::
1143
1165
1144 c.InlineBackend.close_figures = False
1166 c.InlineBackend.close_figures = False
1145
1167
1146 Parameters
1168 Parameters
1147 ----------
1169 ----------
1148 close : bool
1170 close : bool
1149 Should all matplotlib figures be automatically closed after each cell is
1171 Should all matplotlib figures be automatically closed after each cell is
1150 run?
1172 run?
1151 """
1173 """
1152 from ipykernel.pylab.config import InlineBackend
1174 from ipykernel.pylab.config import InlineBackend
1153 cfg = InlineBackend.instance()
1175 cfg = InlineBackend.instance()
1154 cfg.close_figures = close
1176 cfg.close_figures = close
@@ -1,138 +1,138 b''
1 """An interface for publishing rich data to frontends.
1 """An interface for publishing rich data to frontends.
2
2
3 There are two components of the display system:
3 There are two components of the display system:
4
4
5 * Display formatters, which take a Python object and compute the
5 * Display formatters, which take a Python object and compute the
6 representation of the object in various formats (text, HTML, SVG, etc.).
6 representation of the object in various formats (text, HTML, SVG, etc.).
7 * The display publisher that is used to send the representation data to the
7 * The display publisher that is used to send the representation data to the
8 various frontends.
8 various frontends.
9
9
10 This module defines the logic display publishing. The display publisher uses
10 This module defines the logic display publishing. The display publisher uses
11 the ``display_data`` message type that is defined in the IPython messaging
11 the ``display_data`` message type that is defined in the IPython messaging
12 spec.
12 spec.
13 """
13 """
14
14
15 # Copyright (c) IPython Development Team.
15 # Copyright (c) IPython Development Team.
16 # Distributed under the terms of the Modified BSD License.
16 # Distributed under the terms of the Modified BSD License.
17
17
18
18
19 import sys
19 import sys
20
20
21 from traitlets.config.configurable import Configurable
21 from traitlets.config.configurable import Configurable
22 from traitlets import List
22 from traitlets import List
23
23
24 # This used to be defined here - it is imported for backwards compatibility
24 # This used to be defined here - it is imported for backwards compatibility
25 from .display import publish_display_data
25 from .display_functions import publish_display_data
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Main payload class
28 # Main payload class
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class DisplayPublisher(Configurable):
32 class DisplayPublisher(Configurable):
33 """A traited class that publishes display data to frontends.
33 """A traited class that publishes display data to frontends.
34
34
35 Instances of this class are created by the main IPython object and should
35 Instances of this class are created by the main IPython object and should
36 be accessed there.
36 be accessed there.
37 """
37 """
38
38
39 def __init__(self, shell=None, *args, **kwargs):
39 def __init__(self, shell=None, *args, **kwargs):
40 self.shell = shell
40 self.shell = shell
41 super().__init__(*args, **kwargs)
41 super().__init__(*args, **kwargs)
42
42
43 def _validate_data(self, data, metadata=None):
43 def _validate_data(self, data, metadata=None):
44 """Validate the display data.
44 """Validate the display data.
45
45
46 Parameters
46 Parameters
47 ----------
47 ----------
48 data : dict
48 data : dict
49 The formata data dictionary.
49 The formata data dictionary.
50 metadata : dict
50 metadata : dict
51 Any metadata for the data.
51 Any metadata for the data.
52 """
52 """
53
53
54 if not isinstance(data, dict):
54 if not isinstance(data, dict):
55 raise TypeError('data must be a dict, got: %r' % data)
55 raise TypeError('data must be a dict, got: %r' % data)
56 if metadata is not None:
56 if metadata is not None:
57 if not isinstance(metadata, dict):
57 if not isinstance(metadata, dict):
58 raise TypeError('metadata must be a dict, got: %r' % data)
58 raise TypeError('metadata must be a dict, got: %r' % data)
59
59
60 # use * to indicate transient, update are keyword-only
60 # use * to indicate transient, update are keyword-only
61 def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None:
61 def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None:
62 """Publish data and metadata to all frontends.
62 """Publish data and metadata to all frontends.
63
63
64 See the ``display_data`` message in the messaging documentation for
64 See the ``display_data`` message in the messaging documentation for
65 more details about this message type.
65 more details about this message type.
66
66
67 The following MIME types are currently implemented:
67 The following MIME types are currently implemented:
68
68
69 * text/plain
69 * text/plain
70 * text/html
70 * text/html
71 * text/markdown
71 * text/markdown
72 * text/latex
72 * text/latex
73 * application/json
73 * application/json
74 * application/javascript
74 * application/javascript
75 * image/png
75 * image/png
76 * image/jpeg
76 * image/jpeg
77 * image/svg+xml
77 * image/svg+xml
78
78
79 Parameters
79 Parameters
80 ----------
80 ----------
81 data : dict
81 data : dict
82 A dictionary having keys that are valid MIME types (like
82 A dictionary having keys that are valid MIME types (like
83 'text/plain' or 'image/svg+xml') and values that are the data for
83 'text/plain' or 'image/svg+xml') and values that are the data for
84 that MIME type. The data itself must be a JSON'able data
84 that MIME type. The data itself must be a JSON'able data
85 structure. Minimally all data should have the 'text/plain' data,
85 structure. Minimally all data should have the 'text/plain' data,
86 which can be displayed by all frontends. If more than the plain
86 which can be displayed by all frontends. If more than the plain
87 text is given, it is up to the frontend to decide which
87 text is given, it is up to the frontend to decide which
88 representation to use.
88 representation to use.
89 metadata : dict
89 metadata : dict
90 A dictionary for metadata related to the data. This can contain
90 A dictionary for metadata related to the data. This can contain
91 arbitrary key, value pairs that frontends can use to interpret
91 arbitrary key, value pairs that frontends can use to interpret
92 the data. Metadata specific to each mime-type can be specified
92 the data. Metadata specific to each mime-type can be specified
93 in the metadata dict with the same mime-type keys as
93 in the metadata dict with the same mime-type keys as
94 the data itself.
94 the data itself.
95 source : str, deprecated
95 source : str, deprecated
96 Unused.
96 Unused.
97 transient: dict, keyword-only
97 transient: dict, keyword-only
98 A dictionary for transient data.
98 A dictionary for transient data.
99 Data in this dictionary should not be persisted as part of saving this output.
99 Data in this dictionary should not be persisted as part of saving this output.
100 Examples include 'display_id'.
100 Examples include 'display_id'.
101 update: bool, keyword-only, default: False
101 update: bool, keyword-only, default: False
102 If True, only update existing outputs with the same display_id,
102 If True, only update existing outputs with the same display_id,
103 rather than creating a new output.
103 rather than creating a new output.
104 """
104 """
105
105
106 handlers = {}
106 handlers = {}
107 if self.shell is not None:
107 if self.shell is not None:
108 handlers = getattr(self.shell, 'mime_renderers', {})
108 handlers = getattr(self.shell, 'mime_renderers', {})
109
109
110 for mime, handler in handlers.items():
110 for mime, handler in handlers.items():
111 if mime in data:
111 if mime in data:
112 handler(data[mime], metadata.get(mime, None))
112 handler(data[mime], metadata.get(mime, None))
113 return
113 return
114
114
115 if 'text/plain' in data:
115 if 'text/plain' in data:
116 print(data['text/plain'])
116 print(data['text/plain'])
117
117
118 def clear_output(self, wait=False):
118 def clear_output(self, wait=False):
119 """Clear the output of the cell receiving output."""
119 """Clear the output of the cell receiving output."""
120 print('\033[2K\r', end='')
120 print('\033[2K\r', end='')
121 sys.stdout.flush()
121 sys.stdout.flush()
122 print('\033[2K\r', end='')
122 print('\033[2K\r', end='')
123 sys.stderr.flush()
123 sys.stderr.flush()
124
124
125
125
126 class CapturingDisplayPublisher(DisplayPublisher):
126 class CapturingDisplayPublisher(DisplayPublisher):
127 """A DisplayPublisher that stores"""
127 """A DisplayPublisher that stores"""
128 outputs = List()
128 outputs = List()
129
129
130 def publish(self, data, metadata=None, source=None, *, transient=None, update=False):
130 def publish(self, data, metadata=None, source=None, *, transient=None, update=False):
131 self.outputs.append({'data':data, 'metadata':metadata,
131 self.outputs.append({'data':data, 'metadata':metadata,
132 'transient':transient, 'update':update})
132 'transient':transient, 'update':update})
133
133
134 def clear_output(self, wait=False):
134 def clear_output(self, wait=False):
135 super(CapturingDisplayPublisher, self).clear_output(wait)
135 super(CapturingDisplayPublisher, self).clear_output(wait)
136
136
137 # empty the list, *do not* reassign a new list
137 # empty the list, *do not* reassign a new list
138 self.outputs.clear()
138 self.outputs.clear()
@@ -1,82 +1,82 b''
1 """Simple magics for display formats"""
1 """Simple magics for display formats"""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (c) 2012 The IPython Development Team.
3 # Copyright (c) 2012 The IPython Development Team.
4 #
4 #
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6 #
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # Our own packages
14 # Our own packages
15 from IPython.core.display import display, Javascript, Latex, SVG, HTML, Markdown
15 from IPython.display import display, Javascript, Latex, SVG, HTML, Markdown
16 from IPython.core.magic import (
16 from IPython.core.magic import (
17 Magics, magics_class, cell_magic
17 Magics, magics_class, cell_magic
18 )
18 )
19 from IPython.core import magic_arguments
19 from IPython.core import magic_arguments
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Magic implementation classes
22 # Magic implementation classes
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25
25
26 @magics_class
26 @magics_class
27 class DisplayMagics(Magics):
27 class DisplayMagics(Magics):
28 """Magics for displaying various output types with literals
28 """Magics for displaying various output types with literals
29
29
30 Defines javascript/latex/svg/html cell magics for writing
30 Defines javascript/latex/svg/html cell magics for writing
31 blocks in those languages, to be rendered in the frontend.
31 blocks in those languages, to be rendered in the frontend.
32 """
32 """
33
33
34 @cell_magic
34 @cell_magic
35 def js(self, line, cell):
35 def js(self, line, cell):
36 """Run the cell block of Javascript code
36 """Run the cell block of Javascript code
37
37
38 Alias of `%%javascript`
38 Alias of `%%javascript`
39 """
39 """
40 self.javascript(line, cell)
40 self.javascript(line, cell)
41
41
42 @cell_magic
42 @cell_magic
43 def javascript(self, line, cell):
43 def javascript(self, line, cell):
44 """Run the cell block of Javascript code"""
44 """Run the cell block of Javascript code"""
45 display(Javascript(cell))
45 display(Javascript(cell))
46
46
47
47
48 @cell_magic
48 @cell_magic
49 def latex(self, line, cell):
49 def latex(self, line, cell):
50 """Render the cell as a block of latex
50 """Render the cell as a block of latex
51
51
52 The subset of latex which is support depends on the implementation in
52 The subset of latex which is support depends on the implementation in
53 the client. In the Jupyter Notebook, this magic only renders the subset
53 the client. In the Jupyter Notebook, this magic only renders the subset
54 of latex defined by MathJax
54 of latex defined by MathJax
55 [here](https://docs.mathjax.org/en/v2.5-latest/tex.html)."""
55 [here](https://docs.mathjax.org/en/v2.5-latest/tex.html)."""
56 display(Latex(cell))
56 display(Latex(cell))
57
57
58 @cell_magic
58 @cell_magic
59 def svg(self, line, cell):
59 def svg(self, line, cell):
60 """Render the cell as an SVG literal"""
60 """Render the cell as an SVG literal"""
61 display(SVG(cell))
61 display(SVG(cell))
62
62
63 @magic_arguments.magic_arguments()
63 @magic_arguments.magic_arguments()
64 @magic_arguments.argument(
64 @magic_arguments.argument(
65 '--isolated', action='store_true', default=False,
65 '--isolated', action='store_true', default=False,
66 help="""Annotate the cell as 'isolated'.
66 help="""Annotate the cell as 'isolated'.
67 Isolated cells are rendered inside their own <iframe> tag"""
67 Isolated cells are rendered inside their own <iframe> tag"""
68 )
68 )
69 @cell_magic
69 @cell_magic
70 def html(self, line, cell):
70 def html(self, line, cell):
71 """Render the cell as a block of HTML"""
71 """Render the cell as a block of HTML"""
72 args = magic_arguments.parse_argstring(self.html, line)
72 args = magic_arguments.parse_argstring(self.html, line)
73 html = HTML(cell)
73 html = HTML(cell)
74 if args.isolated:
74 if args.isolated:
75 display(html, metadata={'text/html':{'isolated':True}})
75 display(html, metadata={'text/html':{'isolated':True}})
76 else:
76 else:
77 display(html)
77 display(html)
78
78
79 @cell_magic
79 @cell_magic
80 def markdown(self, line, cell):
80 def markdown(self, line, cell):
81 """Render the cell as Markdown text block"""
81 """Render the cell as Markdown text block"""
82 display(Markdown(cell))
82 display(Markdown(cell))
@@ -1,343 +1,343 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Paging capabilities for IPython.core
3 Paging capabilities for IPython.core
4
4
5 Notes
5 Notes
6 -----
6 -----
7
7
8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
9 rid of that dependency, we could move it there.
9 rid of that dependency, we could move it there.
10 -----
10 -----
11 """
11 """
12
12
13 # Copyright (c) IPython Development Team.
13 # Copyright (c) IPython Development Team.
14 # Distributed under the terms of the Modified BSD License.
14 # Distributed under the terms of the Modified BSD License.
15
15
16
16
17 import os
17 import os
18 import io
18 import io
19 import re
19 import re
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import subprocess
22 import subprocess
23
23
24 from io import UnsupportedOperation
24 from io import UnsupportedOperation
25
25
26 from IPython import get_ipython
26 from IPython import get_ipython
27 from IPython.core.display import display
27 from IPython.display import display
28 from IPython.core.error import TryNext
28 from IPython.core.error import TryNext
29 from IPython.utils.data import chop
29 from IPython.utils.data import chop
30 from IPython.utils.process import system
30 from IPython.utils.process import system
31 from IPython.utils.terminal import get_terminal_size
31 from IPython.utils.terminal import get_terminal_size
32 from IPython.utils import py3compat
32 from IPython.utils import py3compat
33
33
34
34
35 def display_page(strng, start=0, screen_lines=25):
35 def display_page(strng, start=0, screen_lines=25):
36 """Just display, no paging. screen_lines is ignored."""
36 """Just display, no paging. screen_lines is ignored."""
37 if isinstance(strng, dict):
37 if isinstance(strng, dict):
38 data = strng
38 data = strng
39 else:
39 else:
40 if start:
40 if start:
41 strng = u'\n'.join(strng.splitlines()[start:])
41 strng = u'\n'.join(strng.splitlines()[start:])
42 data = { 'text/plain': strng }
42 data = { 'text/plain': strng }
43 display(data, raw=True)
43 display(data, raw=True)
44
44
45
45
46 def as_hook(page_func):
46 def as_hook(page_func):
47 """Wrap a pager func to strip the `self` arg
47 """Wrap a pager func to strip the `self` arg
48
48
49 so it can be called as a hook.
49 so it can be called as a hook.
50 """
50 """
51 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
51 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
52
52
53
53
54 esc_re = re.compile(r"(\x1b[^m]+m)")
54 esc_re = re.compile(r"(\x1b[^m]+m)")
55
55
56 def page_dumb(strng, start=0, screen_lines=25):
56 def page_dumb(strng, start=0, screen_lines=25):
57 """Very dumb 'pager' in Python, for when nothing else works.
57 """Very dumb 'pager' in Python, for when nothing else works.
58
58
59 Only moves forward, same interface as page(), except for pager_cmd and
59 Only moves forward, same interface as page(), except for pager_cmd and
60 mode.
60 mode.
61 """
61 """
62 if isinstance(strng, dict):
62 if isinstance(strng, dict):
63 strng = strng.get('text/plain', '')
63 strng = strng.get('text/plain', '')
64 out_ln = strng.splitlines()[start:]
64 out_ln = strng.splitlines()[start:]
65 screens = chop(out_ln,screen_lines-1)
65 screens = chop(out_ln,screen_lines-1)
66 if len(screens) == 1:
66 if len(screens) == 1:
67 print(os.linesep.join(screens[0]))
67 print(os.linesep.join(screens[0]))
68 else:
68 else:
69 last_escape = ""
69 last_escape = ""
70 for scr in screens[0:-1]:
70 for scr in screens[0:-1]:
71 hunk = os.linesep.join(scr)
71 hunk = os.linesep.join(scr)
72 print(last_escape + hunk)
72 print(last_escape + hunk)
73 if not page_more():
73 if not page_more():
74 return
74 return
75 esc_list = esc_re.findall(hunk)
75 esc_list = esc_re.findall(hunk)
76 if len(esc_list) > 0:
76 if len(esc_list) > 0:
77 last_escape = esc_list[-1]
77 last_escape = esc_list[-1]
78 print(last_escape + os.linesep.join(screens[-1]))
78 print(last_escape + os.linesep.join(screens[-1]))
79
79
80 def _detect_screen_size(screen_lines_def):
80 def _detect_screen_size(screen_lines_def):
81 """Attempt to work out the number of lines on the screen.
81 """Attempt to work out the number of lines on the screen.
82
82
83 This is called by page(). It can raise an error (e.g. when run in the
83 This is called by page(). It can raise an error (e.g. when run in the
84 test suite), so it's separated out so it can easily be called in a try block.
84 test suite), so it's separated out so it can easily be called in a try block.
85 """
85 """
86 TERM = os.environ.get('TERM',None)
86 TERM = os.environ.get('TERM',None)
87 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
87 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
88 # curses causes problems on many terminals other than xterm, and
88 # curses causes problems on many terminals other than xterm, and
89 # some termios calls lock up on Sun OS5.
89 # some termios calls lock up on Sun OS5.
90 return screen_lines_def
90 return screen_lines_def
91
91
92 try:
92 try:
93 import termios
93 import termios
94 import curses
94 import curses
95 except ImportError:
95 except ImportError:
96 return screen_lines_def
96 return screen_lines_def
97
97
98 # There is a bug in curses, where *sometimes* it fails to properly
98 # There is a bug in curses, where *sometimes* it fails to properly
99 # initialize, and then after the endwin() call is made, the
99 # initialize, and then after the endwin() call is made, the
100 # terminal is left in an unusable state. Rather than trying to
100 # terminal is left in an unusable state. Rather than trying to
101 # check every time for this (by requesting and comparing termios
101 # check every time for this (by requesting and comparing termios
102 # flags each time), we just save the initial terminal state and
102 # flags each time), we just save the initial terminal state and
103 # unconditionally reset it every time. It's cheaper than making
103 # unconditionally reset it every time. It's cheaper than making
104 # the checks.
104 # the checks.
105 try:
105 try:
106 term_flags = termios.tcgetattr(sys.stdout)
106 term_flags = termios.tcgetattr(sys.stdout)
107 except termios.error as err:
107 except termios.error as err:
108 # can fail on Linux 2.6, pager_page will catch the TypeError
108 # can fail on Linux 2.6, pager_page will catch the TypeError
109 raise TypeError('termios error: {0}'.format(err))
109 raise TypeError('termios error: {0}'.format(err))
110
110
111 try:
111 try:
112 scr = curses.initscr()
112 scr = curses.initscr()
113 except AttributeError:
113 except AttributeError:
114 # Curses on Solaris may not be complete, so we can't use it there
114 # Curses on Solaris may not be complete, so we can't use it there
115 return screen_lines_def
115 return screen_lines_def
116
116
117 screen_lines_real,screen_cols = scr.getmaxyx()
117 screen_lines_real,screen_cols = scr.getmaxyx()
118 curses.endwin()
118 curses.endwin()
119
119
120 # Restore terminal state in case endwin() didn't.
120 # Restore terminal state in case endwin() didn't.
121 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
121 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
122 # Now we have what we needed: the screen size in rows/columns
122 # Now we have what we needed: the screen size in rows/columns
123 return screen_lines_real
123 return screen_lines_real
124 #print '***Screen size:',screen_lines_real,'lines x',\
124 #print '***Screen size:',screen_lines_real,'lines x',\
125 #screen_cols,'columns.' # dbg
125 #screen_cols,'columns.' # dbg
126
126
127 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):
127 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):
128 """Display a string, piping through a pager after a certain length.
128 """Display a string, piping through a pager after a certain length.
129
129
130 strng can be a mime-bundle dict, supplying multiple representations,
130 strng can be a mime-bundle dict, supplying multiple representations,
131 keyed by mime-type.
131 keyed by mime-type.
132
132
133 The screen_lines parameter specifies the number of *usable* lines of your
133 The screen_lines parameter specifies the number of *usable* lines of your
134 terminal screen (total lines minus lines you need to reserve to show other
134 terminal screen (total lines minus lines you need to reserve to show other
135 information).
135 information).
136
136
137 If you set screen_lines to a number <=0, page() will try to auto-determine
137 If you set screen_lines to a number <=0, page() will try to auto-determine
138 your screen size and will only use up to (screen_size+screen_lines) for
138 your screen size and will only use up to (screen_size+screen_lines) for
139 printing, paging after that. That is, if you want auto-detection but need
139 printing, paging after that. That is, if you want auto-detection but need
140 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
140 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
141 auto-detection without any lines reserved simply use screen_lines = 0.
141 auto-detection without any lines reserved simply use screen_lines = 0.
142
142
143 If a string won't fit in the allowed lines, it is sent through the
143 If a string won't fit in the allowed lines, it is sent through the
144 specified pager command. If none given, look for PAGER in the environment,
144 specified pager command. If none given, look for PAGER in the environment,
145 and ultimately default to less.
145 and ultimately default to less.
146
146
147 If no system pager works, the string is sent through a 'dumb pager'
147 If no system pager works, the string is sent through a 'dumb pager'
148 written in python, very simplistic.
148 written in python, very simplistic.
149 """
149 """
150
150
151 # for compatibility with mime-bundle form:
151 # for compatibility with mime-bundle form:
152 if isinstance(strng, dict):
152 if isinstance(strng, dict):
153 strng = strng['text/plain']
153 strng = strng['text/plain']
154
154
155 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
155 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
156 TERM = os.environ.get('TERM','dumb')
156 TERM = os.environ.get('TERM','dumb')
157 if TERM in ['dumb','emacs'] and os.name != 'nt':
157 if TERM in ['dumb','emacs'] and os.name != 'nt':
158 print(strng)
158 print(strng)
159 return
159 return
160 # chop off the topmost part of the string we don't want to see
160 # chop off the topmost part of the string we don't want to see
161 str_lines = strng.splitlines()[start:]
161 str_lines = strng.splitlines()[start:]
162 str_toprint = os.linesep.join(str_lines)
162 str_toprint = os.linesep.join(str_lines)
163 num_newlines = len(str_lines)
163 num_newlines = len(str_lines)
164 len_str = len(str_toprint)
164 len_str = len(str_toprint)
165
165
166 # Dumb heuristics to guesstimate number of on-screen lines the string
166 # Dumb heuristics to guesstimate number of on-screen lines the string
167 # takes. Very basic, but good enough for docstrings in reasonable
167 # takes. Very basic, but good enough for docstrings in reasonable
168 # terminals. If someone later feels like refining it, it's not hard.
168 # terminals. If someone later feels like refining it, it's not hard.
169 numlines = max(num_newlines,int(len_str/80)+1)
169 numlines = max(num_newlines,int(len_str/80)+1)
170
170
171 screen_lines_def = get_terminal_size()[1]
171 screen_lines_def = get_terminal_size()[1]
172
172
173 # auto-determine screen size
173 # auto-determine screen size
174 if screen_lines <= 0:
174 if screen_lines <= 0:
175 try:
175 try:
176 screen_lines += _detect_screen_size(screen_lines_def)
176 screen_lines += _detect_screen_size(screen_lines_def)
177 except (TypeError, UnsupportedOperation):
177 except (TypeError, UnsupportedOperation):
178 print(str_toprint)
178 print(str_toprint)
179 return
179 return
180
180
181 #print 'numlines',numlines,'screenlines',screen_lines # dbg
181 #print 'numlines',numlines,'screenlines',screen_lines # dbg
182 if numlines <= screen_lines :
182 if numlines <= screen_lines :
183 #print '*** normal print' # dbg
183 #print '*** normal print' # dbg
184 print(str_toprint)
184 print(str_toprint)
185 else:
185 else:
186 # Try to open pager and default to internal one if that fails.
186 # Try to open pager and default to internal one if that fails.
187 # All failure modes are tagged as 'retval=1', to match the return
187 # All failure modes are tagged as 'retval=1', to match the return
188 # value of a failed system command. If any intermediate attempt
188 # value of a failed system command. If any intermediate attempt
189 # sets retval to 1, at the end we resort to our own page_dumb() pager.
189 # sets retval to 1, at the end we resort to our own page_dumb() pager.
190 pager_cmd = get_pager_cmd(pager_cmd)
190 pager_cmd = get_pager_cmd(pager_cmd)
191 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
191 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
192 if os.name == 'nt':
192 if os.name == 'nt':
193 if pager_cmd.startswith('type'):
193 if pager_cmd.startswith('type'):
194 # The default WinXP 'type' command is failing on complex strings.
194 # The default WinXP 'type' command is failing on complex strings.
195 retval = 1
195 retval = 1
196 else:
196 else:
197 fd, tmpname = tempfile.mkstemp('.txt')
197 fd, tmpname = tempfile.mkstemp('.txt')
198 try:
198 try:
199 os.close(fd)
199 os.close(fd)
200 with open(tmpname, 'wt') as tmpfile:
200 with open(tmpname, 'wt') as tmpfile:
201 tmpfile.write(strng)
201 tmpfile.write(strng)
202 cmd = "%s < %s" % (pager_cmd, tmpname)
202 cmd = "%s < %s" % (pager_cmd, tmpname)
203 # tmpfile needs to be closed for windows
203 # tmpfile needs to be closed for windows
204 if os.system(cmd):
204 if os.system(cmd):
205 retval = 1
205 retval = 1
206 else:
206 else:
207 retval = None
207 retval = None
208 finally:
208 finally:
209 os.remove(tmpname)
209 os.remove(tmpname)
210 else:
210 else:
211 try:
211 try:
212 retval = None
212 retval = None
213 # Emulate os.popen, but redirect stderr
213 # Emulate os.popen, but redirect stderr
214 proc = subprocess.Popen(pager_cmd,
214 proc = subprocess.Popen(pager_cmd,
215 shell=True,
215 shell=True,
216 stdin=subprocess.PIPE,
216 stdin=subprocess.PIPE,
217 stderr=subprocess.DEVNULL
217 stderr=subprocess.DEVNULL
218 )
218 )
219 pager = os._wrap_close(io.TextIOWrapper(proc.stdin), proc)
219 pager = os._wrap_close(io.TextIOWrapper(proc.stdin), proc)
220 try:
220 try:
221 pager_encoding = pager.encoding or sys.stdout.encoding
221 pager_encoding = pager.encoding or sys.stdout.encoding
222 pager.write(strng)
222 pager.write(strng)
223 finally:
223 finally:
224 retval = pager.close()
224 retval = pager.close()
225 except IOError as msg: # broken pipe when user quits
225 except IOError as msg: # broken pipe when user quits
226 if msg.args == (32, 'Broken pipe'):
226 if msg.args == (32, 'Broken pipe'):
227 retval = None
227 retval = None
228 else:
228 else:
229 retval = 1
229 retval = 1
230 except OSError:
230 except OSError:
231 # Other strange problems, sometimes seen in Win2k/cygwin
231 # Other strange problems, sometimes seen in Win2k/cygwin
232 retval = 1
232 retval = 1
233 if retval is not None:
233 if retval is not None:
234 page_dumb(strng,screen_lines=screen_lines)
234 page_dumb(strng,screen_lines=screen_lines)
235
235
236
236
237 def page(data, start=0, screen_lines=0, pager_cmd=None):
237 def page(data, start=0, screen_lines=0, pager_cmd=None):
238 """Display content in a pager, piping through a pager after a certain length.
238 """Display content in a pager, piping through a pager after a certain length.
239
239
240 data can be a mime-bundle dict, supplying multiple representations,
240 data can be a mime-bundle dict, supplying multiple representations,
241 keyed by mime-type, or text.
241 keyed by mime-type, or text.
242
242
243 Pager is dispatched via the `show_in_pager` IPython hook.
243 Pager is dispatched via the `show_in_pager` IPython hook.
244 If no hook is registered, `pager_page` will be used.
244 If no hook is registered, `pager_page` will be used.
245 """
245 """
246 # Some routines may auto-compute start offsets incorrectly and pass a
246 # Some routines may auto-compute start offsets incorrectly and pass a
247 # negative value. Offset to 0 for robustness.
247 # negative value. Offset to 0 for robustness.
248 start = max(0, start)
248 start = max(0, start)
249
249
250 # first, try the hook
250 # first, try the hook
251 ip = get_ipython()
251 ip = get_ipython()
252 if ip:
252 if ip:
253 try:
253 try:
254 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
254 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
255 return
255 return
256 except TryNext:
256 except TryNext:
257 pass
257 pass
258
258
259 # fallback on default pager
259 # fallback on default pager
260 return pager_page(data, start, screen_lines, pager_cmd)
260 return pager_page(data, start, screen_lines, pager_cmd)
261
261
262
262
263 def page_file(fname, start=0, pager_cmd=None):
263 def page_file(fname, start=0, pager_cmd=None):
264 """Page a file, using an optional pager command and starting line.
264 """Page a file, using an optional pager command and starting line.
265 """
265 """
266
266
267 pager_cmd = get_pager_cmd(pager_cmd)
267 pager_cmd = get_pager_cmd(pager_cmd)
268 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
268 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
269
269
270 try:
270 try:
271 if os.environ['TERM'] in ['emacs','dumb']:
271 if os.environ['TERM'] in ['emacs','dumb']:
272 raise EnvironmentError
272 raise EnvironmentError
273 system(pager_cmd + ' ' + fname)
273 system(pager_cmd + ' ' + fname)
274 except:
274 except:
275 try:
275 try:
276 if start > 0:
276 if start > 0:
277 start -= 1
277 start -= 1
278 page(open(fname).read(),start)
278 page(open(fname).read(),start)
279 except:
279 except:
280 print('Unable to show file',repr(fname))
280 print('Unable to show file',repr(fname))
281
281
282
282
283 def get_pager_cmd(pager_cmd=None):
283 def get_pager_cmd(pager_cmd=None):
284 """Return a pager command.
284 """Return a pager command.
285
285
286 Makes some attempts at finding an OS-correct one.
286 Makes some attempts at finding an OS-correct one.
287 """
287 """
288 if os.name == 'posix':
288 if os.name == 'posix':
289 default_pager_cmd = 'less -R' # -R for color control sequences
289 default_pager_cmd = 'less -R' # -R for color control sequences
290 elif os.name in ['nt','dos']:
290 elif os.name in ['nt','dos']:
291 default_pager_cmd = 'type'
291 default_pager_cmd = 'type'
292
292
293 if pager_cmd is None:
293 if pager_cmd is None:
294 try:
294 try:
295 pager_cmd = os.environ['PAGER']
295 pager_cmd = os.environ['PAGER']
296 except:
296 except:
297 pager_cmd = default_pager_cmd
297 pager_cmd = default_pager_cmd
298
298
299 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
299 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
300 pager_cmd += ' -R'
300 pager_cmd += ' -R'
301
301
302 return pager_cmd
302 return pager_cmd
303
303
304
304
305 def get_pager_start(pager, start):
305 def get_pager_start(pager, start):
306 """Return the string for paging files with an offset.
306 """Return the string for paging files with an offset.
307
307
308 This is the '+N' argument which less and more (under Unix) accept.
308 This is the '+N' argument which less and more (under Unix) accept.
309 """
309 """
310
310
311 if pager in ['less','more']:
311 if pager in ['less','more']:
312 if start:
312 if start:
313 start_string = '+' + str(start)
313 start_string = '+' + str(start)
314 else:
314 else:
315 start_string = ''
315 start_string = ''
316 else:
316 else:
317 start_string = ''
317 start_string = ''
318 return start_string
318 return start_string
319
319
320
320
321 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
321 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
322 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
322 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
323 import msvcrt
323 import msvcrt
324 def page_more():
324 def page_more():
325 """ Smart pausing between pages
325 """ Smart pausing between pages
326
326
327 @return: True if need print more lines, False if quit
327 @return: True if need print more lines, False if quit
328 """
328 """
329 sys.stdout.write('---Return to continue, q to quit--- ')
329 sys.stdout.write('---Return to continue, q to quit--- ')
330 ans = msvcrt.getwch()
330 ans = msvcrt.getwch()
331 if ans in ("q", "Q"):
331 if ans in ("q", "Q"):
332 result = False
332 result = False
333 else:
333 else:
334 result = True
334 result = True
335 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
335 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
336 return result
336 return result
337 else:
337 else:
338 def page_more():
338 def page_more():
339 ans = py3compat.input('---Return to continue, q to quit--- ')
339 ans = py3compat.input('---Return to continue, q to quit--- ')
340 if ans.lower().startswith('q'):
340 if ans.lower().startswith('q'):
341 return False
341 return False
342 else:
342 else:
343 return True
343 return True
@@ -1,419 +1,419 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Pylab (matplotlib) support utilities."""
2 """Pylab (matplotlib) support utilities."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from io import BytesIO
7 from io import BytesIO
8
8
9 from IPython.core.display import _pngxy
9 from IPython.core.display import _pngxy
10 from IPython.utils.decorators import flag_calls
10 from IPython.utils.decorators import flag_calls
11
11
12 # If user specifies a GUI, that dictates the backend, otherwise we read the
12 # If user specifies a GUI, that dictates the backend, otherwise we read the
13 # user's mpl default from the mpl rc structure
13 # user's mpl default from the mpl rc structure
14 backends = {'tk': 'TkAgg',
14 backends = {'tk': 'TkAgg',
15 'gtk': 'GTKAgg',
15 'gtk': 'GTKAgg',
16 'gtk3': 'GTK3Agg',
16 'gtk3': 'GTK3Agg',
17 'wx': 'WXAgg',
17 'wx': 'WXAgg',
18 'qt4': 'Qt4Agg',
18 'qt4': 'Qt4Agg',
19 'qt5': 'Qt5Agg',
19 'qt5': 'Qt5Agg',
20 'qt': 'Qt5Agg',
20 'qt': 'Qt5Agg',
21 'osx': 'MacOSX',
21 'osx': 'MacOSX',
22 'nbagg': 'nbAgg',
22 'nbagg': 'nbAgg',
23 'notebook': 'nbAgg',
23 'notebook': 'nbAgg',
24 'agg': 'agg',
24 'agg': 'agg',
25 'svg': 'svg',
25 'svg': 'svg',
26 'pdf': 'pdf',
26 'pdf': 'pdf',
27 'ps': 'ps',
27 'ps': 'ps',
28 'inline': 'module://ipykernel.pylab.backend_inline',
28 'inline': 'module://ipykernel.pylab.backend_inline',
29 'ipympl': 'module://ipympl.backend_nbagg',
29 'ipympl': 'module://ipympl.backend_nbagg',
30 'widget': 'module://ipympl.backend_nbagg',
30 'widget': 'module://ipympl.backend_nbagg',
31 }
31 }
32
32
33 # We also need a reverse backends2guis mapping that will properly choose which
33 # We also need a reverse backends2guis mapping that will properly choose which
34 # GUI support to activate based on the desired matplotlib backend. For the
34 # GUI support to activate based on the desired matplotlib backend. For the
35 # most part it's just a reverse of the above dict, but we also need to add a
35 # most part it's just a reverse of the above dict, but we also need to add a
36 # few others that map to the same GUI manually:
36 # few others that map to the same GUI manually:
37 backend2gui = dict(zip(backends.values(), backends.keys()))
37 backend2gui = dict(zip(backends.values(), backends.keys()))
38 # Our tests expect backend2gui to just return 'qt'
38 # Our tests expect backend2gui to just return 'qt'
39 backend2gui['Qt4Agg'] = 'qt'
39 backend2gui['Qt4Agg'] = 'qt'
40 # In the reverse mapping, there are a few extra valid matplotlib backends that
40 # In the reverse mapping, there are a few extra valid matplotlib backends that
41 # map to the same GUI support
41 # map to the same GUI support
42 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
42 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
43 backend2gui['GTK3Cairo'] = 'gtk3'
43 backend2gui['GTK3Cairo'] = 'gtk3'
44 backend2gui['WX'] = 'wx'
44 backend2gui['WX'] = 'wx'
45 backend2gui['CocoaAgg'] = 'osx'
45 backend2gui['CocoaAgg'] = 'osx'
46 # And some backends that don't need GUI integration
46 # And some backends that don't need GUI integration
47 del backend2gui['nbAgg']
47 del backend2gui['nbAgg']
48 del backend2gui['agg']
48 del backend2gui['agg']
49 del backend2gui['svg']
49 del backend2gui['svg']
50 del backend2gui['pdf']
50 del backend2gui['pdf']
51 del backend2gui['ps']
51 del backend2gui['ps']
52 del backend2gui['module://ipykernel.pylab.backend_inline']
52 del backend2gui['module://ipykernel.pylab.backend_inline']
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Matplotlib utilities
55 # Matplotlib utilities
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58
58
59 def getfigs(*fig_nums):
59 def getfigs(*fig_nums):
60 """Get a list of matplotlib figures by figure numbers.
60 """Get a list of matplotlib figures by figure numbers.
61
61
62 If no arguments are given, all available figures are returned. If the
62 If no arguments are given, all available figures are returned. If the
63 argument list contains references to invalid figures, a warning is printed
63 argument list contains references to invalid figures, a warning is printed
64 but the function continues pasting further figures.
64 but the function continues pasting further figures.
65
65
66 Parameters
66 Parameters
67 ----------
67 ----------
68 figs : tuple
68 figs : tuple
69 A tuple of ints giving the figure numbers of the figures to return.
69 A tuple of ints giving the figure numbers of the figures to return.
70 """
70 """
71 from matplotlib._pylab_helpers import Gcf
71 from matplotlib._pylab_helpers import Gcf
72 if not fig_nums:
72 if not fig_nums:
73 fig_managers = Gcf.get_all_fig_managers()
73 fig_managers = Gcf.get_all_fig_managers()
74 return [fm.canvas.figure for fm in fig_managers]
74 return [fm.canvas.figure for fm in fig_managers]
75 else:
75 else:
76 figs = []
76 figs = []
77 for num in fig_nums:
77 for num in fig_nums:
78 f = Gcf.figs.get(num)
78 f = Gcf.figs.get(num)
79 if f is None:
79 if f is None:
80 print('Warning: figure %s not available.' % num)
80 print('Warning: figure %s not available.' % num)
81 else:
81 else:
82 figs.append(f.canvas.figure)
82 figs.append(f.canvas.figure)
83 return figs
83 return figs
84
84
85
85
86 def figsize(sizex, sizey):
86 def figsize(sizex, sizey):
87 """Set the default figure size to be [sizex, sizey].
87 """Set the default figure size to be [sizex, sizey].
88
88
89 This is just an easy to remember, convenience wrapper that sets::
89 This is just an easy to remember, convenience wrapper that sets::
90
90
91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 """
92 """
93 import matplotlib
93 import matplotlib
94 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
94 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95
95
96
96
97 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
97 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
98 """Print a figure to an image, and return the resulting file data
98 """Print a figure to an image, and return the resulting file data
99
99
100 Returned data will be bytes unless ``fmt='svg'``,
100 Returned data will be bytes unless ``fmt='svg'``,
101 in which case it will be unicode.
101 in which case it will be unicode.
102
102
103 Any keyword args are passed to fig.canvas.print_figure,
103 Any keyword args are passed to fig.canvas.print_figure,
104 such as ``quality`` or ``bbox_inches``.
104 such as ``quality`` or ``bbox_inches``.
105 """
105 """
106 # When there's an empty figure, we shouldn't return anything, otherwise we
106 # When there's an empty figure, we shouldn't return anything, otherwise we
107 # get big blank areas in the qt console.
107 # get big blank areas in the qt console.
108 if not fig.axes and not fig.lines:
108 if not fig.axes and not fig.lines:
109 return
109 return
110
110
111 dpi = fig.dpi
111 dpi = fig.dpi
112 if fmt == 'retina':
112 if fmt == 'retina':
113 dpi = dpi * 2
113 dpi = dpi * 2
114 fmt = 'png'
114 fmt = 'png'
115
115
116 # build keyword args
116 # build keyword args
117 kw = {
117 kw = {
118 "format":fmt,
118 "format":fmt,
119 "facecolor":fig.get_facecolor(),
119 "facecolor":fig.get_facecolor(),
120 "edgecolor":fig.get_edgecolor(),
120 "edgecolor":fig.get_edgecolor(),
121 "dpi":dpi,
121 "dpi":dpi,
122 "bbox_inches":bbox_inches,
122 "bbox_inches":bbox_inches,
123 }
123 }
124 # **kwargs get higher priority
124 # **kwargs get higher priority
125 kw.update(kwargs)
125 kw.update(kwargs)
126
126
127 bytes_io = BytesIO()
127 bytes_io = BytesIO()
128 if fig.canvas is None:
128 if fig.canvas is None:
129 from matplotlib.backend_bases import FigureCanvasBase
129 from matplotlib.backend_bases import FigureCanvasBase
130 FigureCanvasBase(fig)
130 FigureCanvasBase(fig)
131
131
132 fig.canvas.print_figure(bytes_io, **kw)
132 fig.canvas.print_figure(bytes_io, **kw)
133 data = bytes_io.getvalue()
133 data = bytes_io.getvalue()
134 if fmt == 'svg':
134 if fmt == 'svg':
135 data = data.decode('utf-8')
135 data = data.decode('utf-8')
136 return data
136 return data
137
137
138 def retina_figure(fig, **kwargs):
138 def retina_figure(fig, **kwargs):
139 """format a figure as a pixel-doubled (retina) PNG"""
139 """format a figure as a pixel-doubled (retina) PNG"""
140 pngdata = print_figure(fig, fmt='retina', **kwargs)
140 pngdata = print_figure(fig, fmt='retina', **kwargs)
141 # Make sure that retina_figure acts just like print_figure and returns
141 # Make sure that retina_figure acts just like print_figure and returns
142 # None when the figure is empty.
142 # None when the figure is empty.
143 if pngdata is None:
143 if pngdata is None:
144 return
144 return
145 w, h = _pngxy(pngdata)
145 w, h = _pngxy(pngdata)
146 metadata = {"width": w//2, "height":h//2}
146 metadata = {"width": w//2, "height":h//2}
147 return pngdata, metadata
147 return pngdata, metadata
148
148
149 # We need a little factory function here to create the closure where
149 # We need a little factory function here to create the closure where
150 # safe_execfile can live.
150 # safe_execfile can live.
151 def mpl_runner(safe_execfile):
151 def mpl_runner(safe_execfile):
152 """Factory to return a matplotlib-enabled runner for %run.
152 """Factory to return a matplotlib-enabled runner for %run.
153
153
154 Parameters
154 Parameters
155 ----------
155 ----------
156 safe_execfile : function
156 safe_execfile : function
157 This must be a function with the same interface as the
157 This must be a function with the same interface as the
158 :meth:`safe_execfile` method of IPython.
158 :meth:`safe_execfile` method of IPython.
159
159
160 Returns
160 Returns
161 -------
161 -------
162 A function suitable for use as the ``runner`` argument of the %run magic
162 A function suitable for use as the ``runner`` argument of the %run magic
163 function.
163 function.
164 """
164 """
165
165
166 def mpl_execfile(fname,*where,**kw):
166 def mpl_execfile(fname,*where,**kw):
167 """matplotlib-aware wrapper around safe_execfile.
167 """matplotlib-aware wrapper around safe_execfile.
168
168
169 Its interface is identical to that of the :func:`execfile` builtin.
169 Its interface is identical to that of the :func:`execfile` builtin.
170
170
171 This is ultimately a call to execfile(), but wrapped in safeties to
171 This is ultimately a call to execfile(), but wrapped in safeties to
172 properly handle interactive rendering."""
172 properly handle interactive rendering."""
173
173
174 import matplotlib
174 import matplotlib
175 import matplotlib.pyplot as plt
175 import matplotlib.pyplot as plt
176
176
177 #print '*** Matplotlib runner ***' # dbg
177 #print '*** Matplotlib runner ***' # dbg
178 # turn off rendering until end of script
178 # turn off rendering until end of script
179 is_interactive = matplotlib.rcParams['interactive']
179 is_interactive = matplotlib.rcParams['interactive']
180 matplotlib.interactive(False)
180 matplotlib.interactive(False)
181 safe_execfile(fname,*where,**kw)
181 safe_execfile(fname,*where,**kw)
182 matplotlib.interactive(is_interactive)
182 matplotlib.interactive(is_interactive)
183 # make rendering call now, if the user tried to do it
183 # make rendering call now, if the user tried to do it
184 if plt.draw_if_interactive.called:
184 if plt.draw_if_interactive.called:
185 plt.draw()
185 plt.draw()
186 plt.draw_if_interactive.called = False
186 plt.draw_if_interactive.called = False
187
187
188 # re-draw everything that is stale
188 # re-draw everything that is stale
189 try:
189 try:
190 da = plt.draw_all
190 da = plt.draw_all
191 except AttributeError:
191 except AttributeError:
192 pass
192 pass
193 else:
193 else:
194 da()
194 da()
195
195
196 return mpl_execfile
196 return mpl_execfile
197
197
198
198
199 def _reshow_nbagg_figure(fig):
199 def _reshow_nbagg_figure(fig):
200 """reshow an nbagg figure"""
200 """reshow an nbagg figure"""
201 try:
201 try:
202 reshow = fig.canvas.manager.reshow
202 reshow = fig.canvas.manager.reshow
203 except AttributeError:
203 except AttributeError:
204 raise NotImplementedError()
204 raise NotImplementedError()
205 else:
205 else:
206 reshow()
206 reshow()
207
207
208
208
209 def select_figure_formats(shell, formats, **kwargs):
209 def select_figure_formats(shell, formats, **kwargs):
210 """Select figure formats for the inline backend.
210 """Select figure formats for the inline backend.
211
211
212 Parameters
212 Parameters
213 ==========
213 ==========
214 shell : InteractiveShell
214 shell : InteractiveShell
215 The main IPython instance.
215 The main IPython instance.
216 formats : str or set
216 formats : str or set
217 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
217 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
218 **kwargs : any
218 **kwargs : any
219 Extra keyword arguments to be passed to fig.canvas.print_figure.
219 Extra keyword arguments to be passed to fig.canvas.print_figure.
220 """
220 """
221 import matplotlib
221 import matplotlib
222 from matplotlib.figure import Figure
222 from matplotlib.figure import Figure
223
223
224 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
224 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
225 png_formatter = shell.display_formatter.formatters['image/png']
225 png_formatter = shell.display_formatter.formatters['image/png']
226 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
226 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
227 pdf_formatter = shell.display_formatter.formatters['application/pdf']
227 pdf_formatter = shell.display_formatter.formatters['application/pdf']
228
228
229 if isinstance(formats, str):
229 if isinstance(formats, str):
230 formats = {formats}
230 formats = {formats}
231 # cast in case of list / tuple
231 # cast in case of list / tuple
232 formats = set(formats)
232 formats = set(formats)
233
233
234 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
234 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
235 mplbackend = matplotlib.get_backend().lower()
235 mplbackend = matplotlib.get_backend().lower()
236 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
236 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
237 formatter = shell.display_formatter.ipython_display_formatter
237 formatter = shell.display_formatter.ipython_display_formatter
238 formatter.for_type(Figure, _reshow_nbagg_figure)
238 formatter.for_type(Figure, _reshow_nbagg_figure)
239
239
240 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
240 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
241 bad = formats.difference(supported)
241 bad = formats.difference(supported)
242 if bad:
242 if bad:
243 bs = "%s" % ','.join([repr(f) for f in bad])
243 bs = "%s" % ','.join([repr(f) for f in bad])
244 gs = "%s" % ','.join([repr(f) for f in supported])
244 gs = "%s" % ','.join([repr(f) for f in supported])
245 raise ValueError("supported formats are: %s not %s" % (gs, bs))
245 raise ValueError("supported formats are: %s not %s" % (gs, bs))
246
246
247 if 'png' in formats:
247 if 'png' in formats:
248 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
248 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
249 if 'retina' in formats or 'png2x' in formats:
249 if 'retina' in formats or 'png2x' in formats:
250 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
250 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
251 if 'jpg' in formats or 'jpeg' in formats:
251 if 'jpg' in formats or 'jpeg' in formats:
252 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
252 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
253 if 'svg' in formats:
253 if 'svg' in formats:
254 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
254 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
255 if 'pdf' in formats:
255 if 'pdf' in formats:
256 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
256 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
257
257
258 #-----------------------------------------------------------------------------
258 #-----------------------------------------------------------------------------
259 # Code for initializing matplotlib and importing pylab
259 # Code for initializing matplotlib and importing pylab
260 #-----------------------------------------------------------------------------
260 #-----------------------------------------------------------------------------
261
261
262
262
263 def find_gui_and_backend(gui=None, gui_select=None):
263 def find_gui_and_backend(gui=None, gui_select=None):
264 """Given a gui string return the gui and mpl backend.
264 """Given a gui string return the gui and mpl backend.
265
265
266 Parameters
266 Parameters
267 ----------
267 ----------
268 gui : str
268 gui : str
269 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
269 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
270 gui_select : str
270 gui_select : str
271 Can be one of ('tk','gtk','wx','qt','qt4','inline').
271 Can be one of ('tk','gtk','wx','qt','qt4','inline').
272 This is any gui already selected by the shell.
272 This is any gui already selected by the shell.
273
273
274 Returns
274 Returns
275 -------
275 -------
276 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
276 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
277 'WXAgg','Qt4Agg','module://ipykernel.pylab.backend_inline','agg').
277 'WXAgg','Qt4Agg','module://ipykernel.pylab.backend_inline','agg').
278 """
278 """
279
279
280 import matplotlib
280 import matplotlib
281
281
282 if gui and gui != 'auto':
282 if gui and gui != 'auto':
283 # select backend based on requested gui
283 # select backend based on requested gui
284 backend = backends[gui]
284 backend = backends[gui]
285 if gui == 'agg':
285 if gui == 'agg':
286 gui = None
286 gui = None
287 else:
287 else:
288 # We need to read the backend from the original data structure, *not*
288 # We need to read the backend from the original data structure, *not*
289 # from mpl.rcParams, since a prior invocation of %matplotlib may have
289 # from mpl.rcParams, since a prior invocation of %matplotlib may have
290 # overwritten that.
290 # overwritten that.
291 # WARNING: this assumes matplotlib 1.1 or newer!!
291 # WARNING: this assumes matplotlib 1.1 or newer!!
292 backend = matplotlib.rcParamsOrig['backend']
292 backend = matplotlib.rcParamsOrig['backend']
293 # In this case, we need to find what the appropriate gui selection call
293 # In this case, we need to find what the appropriate gui selection call
294 # should be for IPython, so we can activate inputhook accordingly
294 # should be for IPython, so we can activate inputhook accordingly
295 gui = backend2gui.get(backend, None)
295 gui = backend2gui.get(backend, None)
296
296
297 # If we have already had a gui active, we need it and inline are the
297 # If we have already had a gui active, we need it and inline are the
298 # ones allowed.
298 # ones allowed.
299 if gui_select and gui != gui_select:
299 if gui_select and gui != gui_select:
300 gui = gui_select
300 gui = gui_select
301 backend = backends[gui]
301 backend = backends[gui]
302
302
303 return gui, backend
303 return gui, backend
304
304
305
305
306 def activate_matplotlib(backend):
306 def activate_matplotlib(backend):
307 """Activate the given backend and set interactive to True."""
307 """Activate the given backend and set interactive to True."""
308
308
309 import matplotlib
309 import matplotlib
310 matplotlib.interactive(True)
310 matplotlib.interactive(True)
311
311
312 # Matplotlib had a bug where even switch_backend could not force
312 # Matplotlib had a bug where even switch_backend could not force
313 # the rcParam to update. This needs to be set *before* the module
313 # the rcParam to update. This needs to be set *before* the module
314 # magic of switch_backend().
314 # magic of switch_backend().
315 matplotlib.rcParams['backend'] = backend
315 matplotlib.rcParams['backend'] = backend
316
316
317 # Due to circular imports, pyplot may be only partially initialised
317 # Due to circular imports, pyplot may be only partially initialised
318 # when this function runs.
318 # when this function runs.
319 # So avoid needing matplotlib attribute-lookup to access pyplot.
319 # So avoid needing matplotlib attribute-lookup to access pyplot.
320 from matplotlib import pyplot as plt
320 from matplotlib import pyplot as plt
321
321
322 plt.switch_backend(backend)
322 plt.switch_backend(backend)
323
323
324 plt.show._needmain = False
324 plt.show._needmain = False
325 # We need to detect at runtime whether show() is called by the user.
325 # We need to detect at runtime whether show() is called by the user.
326 # For this, we wrap it into a decorator which adds a 'called' flag.
326 # For this, we wrap it into a decorator which adds a 'called' flag.
327 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
327 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
328
328
329
329
330 def import_pylab(user_ns, import_all=True):
330 def import_pylab(user_ns, import_all=True):
331 """Populate the namespace with pylab-related values.
331 """Populate the namespace with pylab-related values.
332
332
333 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
333 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
334
334
335 Also imports a few names from IPython (figsize, display, getfigs)
335 Also imports a few names from IPython (figsize, display, getfigs)
336
336
337 """
337 """
338
338
339 # Import numpy as np/pyplot as plt are conventions we're trying to
339 # Import numpy as np/pyplot as plt are conventions we're trying to
340 # somewhat standardize on. Making them available to users by default
340 # somewhat standardize on. Making them available to users by default
341 # will greatly help this.
341 # will greatly help this.
342 s = ("import numpy\n"
342 s = ("import numpy\n"
343 "import matplotlib\n"
343 "import matplotlib\n"
344 "from matplotlib import pylab, mlab, pyplot\n"
344 "from matplotlib import pylab, mlab, pyplot\n"
345 "np = numpy\n"
345 "np = numpy\n"
346 "plt = pyplot\n"
346 "plt = pyplot\n"
347 )
347 )
348 exec(s, user_ns)
348 exec(s, user_ns)
349
349
350 if import_all:
350 if import_all:
351 s = ("from matplotlib.pylab import *\n"
351 s = ("from matplotlib.pylab import *\n"
352 "from numpy import *\n")
352 "from numpy import *\n")
353 exec(s, user_ns)
353 exec(s, user_ns)
354
354
355 # IPython symbols to add
355 # IPython symbols to add
356 user_ns['figsize'] = figsize
356 user_ns['figsize'] = figsize
357 from IPython.core.display import display
357 from IPython.display import display
358 # Add display and getfigs to the user's namespace
358 # Add display and getfigs to the user's namespace
359 user_ns['display'] = display
359 user_ns['display'] = display
360 user_ns['getfigs'] = getfigs
360 user_ns['getfigs'] = getfigs
361
361
362
362
363 def configure_inline_support(shell, backend):
363 def configure_inline_support(shell, backend):
364 """Configure an IPython shell object for matplotlib use.
364 """Configure an IPython shell object for matplotlib use.
365
365
366 Parameters
366 Parameters
367 ----------
367 ----------
368 shell : InteractiveShell instance
368 shell : InteractiveShell instance
369
369
370 backend : matplotlib backend
370 backend : matplotlib backend
371 """
371 """
372 # If using our svg payload backend, register the post-execution
372 # If using our svg payload backend, register the post-execution
373 # function that will pick up the results for display. This can only be
373 # function that will pick up the results for display. This can only be
374 # done with access to the real shell object.
374 # done with access to the real shell object.
375
375
376 # Note: if we can't load the inline backend, then there's no point
376 # Note: if we can't load the inline backend, then there's no point
377 # continuing (such as in terminal-only shells in environments without
377 # continuing (such as in terminal-only shells in environments without
378 # zeromq available).
378 # zeromq available).
379 try:
379 try:
380 from ipykernel.pylab.backend_inline import InlineBackend
380 from ipykernel.pylab.backend_inline import InlineBackend
381 except ImportError:
381 except ImportError:
382 return
382 return
383 import matplotlib
383 import matplotlib
384
384
385 cfg = InlineBackend.instance(parent=shell)
385 cfg = InlineBackend.instance(parent=shell)
386 cfg.shell = shell
386 cfg.shell = shell
387 if cfg not in shell.configurables:
387 if cfg not in shell.configurables:
388 shell.configurables.append(cfg)
388 shell.configurables.append(cfg)
389
389
390 if backend == backends['inline']:
390 if backend == backends['inline']:
391 from ipykernel.pylab.backend_inline import flush_figures
391 from ipykernel.pylab.backend_inline import flush_figures
392 shell.events.register('post_execute', flush_figures)
392 shell.events.register('post_execute', flush_figures)
393
393
394 # Save rcParams that will be overwrittern
394 # Save rcParams that will be overwrittern
395 shell._saved_rcParams = {}
395 shell._saved_rcParams = {}
396 for k in cfg.rc:
396 for k in cfg.rc:
397 shell._saved_rcParams[k] = matplotlib.rcParams[k]
397 shell._saved_rcParams[k] = matplotlib.rcParams[k]
398 # load inline_rc
398 # load inline_rc
399 matplotlib.rcParams.update(cfg.rc)
399 matplotlib.rcParams.update(cfg.rc)
400 new_backend_name = "inline"
400 new_backend_name = "inline"
401 else:
401 else:
402 from ipykernel.pylab.backend_inline import flush_figures
402 from ipykernel.pylab.backend_inline import flush_figures
403 try:
403 try:
404 shell.events.unregister('post_execute', flush_figures)
404 shell.events.unregister('post_execute', flush_figures)
405 except ValueError:
405 except ValueError:
406 pass
406 pass
407 if hasattr(shell, '_saved_rcParams'):
407 if hasattr(shell, '_saved_rcParams'):
408 matplotlib.rcParams.update(shell._saved_rcParams)
408 matplotlib.rcParams.update(shell._saved_rcParams)
409 del shell._saved_rcParams
409 del shell._saved_rcParams
410 new_backend_name = "other"
410 new_backend_name = "other"
411
411
412 # only enable the formats once -> don't change the enabled formats (which the user may
412 # only enable the formats once -> don't change the enabled formats (which the user may
413 # has changed) when getting another "%matplotlib inline" call.
413 # has changed) when getting another "%matplotlib inline" call.
414 # See https://github.com/ipython/ipykernel/issues/29
414 # See https://github.com/ipython/ipykernel/issues/29
415 cur_backend = getattr(configure_inline_support, "current_backend", "unset")
415 cur_backend = getattr(configure_inline_support, "current_backend", "unset")
416 if new_backend_name != cur_backend:
416 if new_backend_name != cur_backend:
417 # Setup the default figure format
417 # Setup the default figure format
418 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
418 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
419 configure_inline_support.current_backend = new_backend_name
419 configure_inline_support.current_backend = new_backend_name
@@ -1,421 +1,421 b''
1 # Copyright (c) IPython Development Team.
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
2 # Distributed under the terms of the Modified BSD License.
3
3
4 import json
4 import json
5 import os
5 import os
6 import warnings
6 import warnings
7
7
8 from unittest import mock
8 from unittest import mock
9
9
10 import nose.tools as nt
10 import nose.tools as nt
11
11
12 from IPython.core import display
12 from IPython import display
13 from IPython.core.getipython import get_ipython
13 from IPython.core.getipython import get_ipython
14 from IPython.utils.io import capture_output
14 from IPython.utils.io import capture_output
15 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
15 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
16 from IPython import paths as ipath
16 from IPython import paths as ipath
17 from IPython.testing.tools import AssertNotPrints
17 from IPython.testing.tools import AssertNotPrints
18
18
19 import IPython.testing.decorators as dec
19 import IPython.testing.decorators as dec
20
20
21 def test_image_size():
21 def test_image_size():
22 """Simple test for display.Image(args, width=x,height=y)"""
22 """Simple test for display.Image(args, width=x,height=y)"""
23 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
23 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
24 img = display.Image(url=thisurl, width=200, height=200)
24 img = display.Image(url=thisurl, width=200, height=200)
25 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
25 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
26 img = display.Image(url=thisurl, metadata={'width':200, 'height':200})
26 img = display.Image(url=thisurl, metadata={'width':200, 'height':200})
27 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
27 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
28 img = display.Image(url=thisurl, width=200)
28 img = display.Image(url=thisurl, width=200)
29 nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
29 nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
30 img = display.Image(url=thisurl)
30 img = display.Image(url=thisurl)
31 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
31 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
32 img = display.Image(url=thisurl, unconfined=True)
32 img = display.Image(url=thisurl, unconfined=True)
33 nt.assert_equal(u'<img src="%s" class="unconfined"/>' % (thisurl), img._repr_html_())
33 nt.assert_equal(u'<img src="%s" class="unconfined"/>' % (thisurl), img._repr_html_())
34
34
35
35
36 def test_image_mimes():
36 def test_image_mimes():
37 fmt = get_ipython().display_formatter.format
37 fmt = get_ipython().display_formatter.format
38 for format in display.Image._ACCEPTABLE_EMBEDDINGS:
38 for format in display.Image._ACCEPTABLE_EMBEDDINGS:
39 mime = display.Image._MIMETYPES[format]
39 mime = display.Image._MIMETYPES[format]
40 img = display.Image(b'garbage', format=format)
40 img = display.Image(b'garbage', format=format)
41 data, metadata = fmt(img)
41 data, metadata = fmt(img)
42 nt.assert_equal(sorted(data), sorted([mime, 'text/plain']))
42 nt.assert_equal(sorted(data), sorted([mime, 'text/plain']))
43
43
44
44
45 def test_geojson():
45 def test_geojson():
46
46
47 gj = display.GeoJSON(data={
47 gj = display.GeoJSON(data={
48 "type": "Feature",
48 "type": "Feature",
49 "geometry": {
49 "geometry": {
50 "type": "Point",
50 "type": "Point",
51 "coordinates": [-81.327, 296.038]
51 "coordinates": [-81.327, 296.038]
52 },
52 },
53 "properties": {
53 "properties": {
54 "name": "Inca City"
54 "name": "Inca City"
55 }
55 }
56 },
56 },
57 url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
57 url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
58 layer_options={
58 layer_options={
59 "basemap_id": "celestia_mars-shaded-16k_global",
59 "basemap_id": "celestia_mars-shaded-16k_global",
60 "attribution": "Celestia/praesepe",
60 "attribution": "Celestia/praesepe",
61 "minZoom": 0,
61 "minZoom": 0,
62 "maxZoom": 18,
62 "maxZoom": 18,
63 })
63 })
64 nt.assert_equal(u'<IPython.core.display.GeoJSON object>', str(gj))
64 nt.assert_equal(u'<IPython.core.display.GeoJSON object>', str(gj))
65
65
66 def test_retina_png():
66 def test_retina_png():
67 here = os.path.dirname(__file__)
67 here = os.path.dirname(__file__)
68 img = display.Image(os.path.join(here, "2x2.png"), retina=True)
68 img = display.Image(os.path.join(here, "2x2.png"), retina=True)
69 nt.assert_equal(img.height, 1)
69 nt.assert_equal(img.height, 1)
70 nt.assert_equal(img.width, 1)
70 nt.assert_equal(img.width, 1)
71 data, md = img._repr_png_()
71 data, md = img._repr_png_()
72 nt.assert_equal(md['width'], 1)
72 nt.assert_equal(md['width'], 1)
73 nt.assert_equal(md['height'], 1)
73 nt.assert_equal(md['height'], 1)
74
74
75 def test_retina_jpeg():
75 def test_retina_jpeg():
76 here = os.path.dirname(__file__)
76 here = os.path.dirname(__file__)
77 img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
77 img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
78 nt.assert_equal(img.height, 1)
78 nt.assert_equal(img.height, 1)
79 nt.assert_equal(img.width, 1)
79 nt.assert_equal(img.width, 1)
80 data, md = img._repr_jpeg_()
80 data, md = img._repr_jpeg_()
81 nt.assert_equal(md['width'], 1)
81 nt.assert_equal(md['width'], 1)
82 nt.assert_equal(md['height'], 1)
82 nt.assert_equal(md['height'], 1)
83
83
84 def test_base64image():
84 def test_base64image():
85 display.Image("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC")
85 display.Image("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC")
86
86
87 def test_image_filename_defaults():
87 def test_image_filename_defaults():
88 '''test format constraint, and validity of jpeg and png'''
88 '''test format constraint, and validity of jpeg and png'''
89 tpath = ipath.get_ipython_package_dir()
89 tpath = ipath.get_ipython_package_dir()
90 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.zip'),
90 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.zip'),
91 embed=True)
91 embed=True)
92 nt.assert_raises(ValueError, display.Image)
92 nt.assert_raises(ValueError, display.Image)
93 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
93 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
94 # check boths paths to allow packages to test at build and install time
94 # check boths paths to allow packages to test at build and install time
95 imgfile = os.path.join(tpath, 'core/tests/2x2.png')
95 imgfile = os.path.join(tpath, 'core/tests/2x2.png')
96 img = display.Image(filename=imgfile)
96 img = display.Image(filename=imgfile)
97 nt.assert_equal('png', img.format)
97 nt.assert_equal('png', img.format)
98 nt.assert_is_not_none(img._repr_png_())
98 nt.assert_is_not_none(img._repr_png_())
99 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
99 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
100 nt.assert_equal('jpeg', img.format)
100 nt.assert_equal('jpeg', img.format)
101 nt.assert_is_none(img._repr_jpeg_())
101 nt.assert_is_none(img._repr_jpeg_())
102
102
103 def _get_inline_config():
103 def _get_inline_config():
104 from ipykernel.pylab.config import InlineBackend
104 from ipykernel.pylab.config import InlineBackend
105 return InlineBackend.instance()
105 return InlineBackend.instance()
106
106
107 @dec.skip_without('matplotlib')
107 @dec.skip_without('matplotlib')
108 def test_set_matplotlib_close():
108 def test_set_matplotlib_close():
109 cfg = _get_inline_config()
109 cfg = _get_inline_config()
110 cfg.close_figures = False
110 cfg.close_figures = False
111 display.set_matplotlib_close()
111 display.set_matplotlib_close()
112 assert cfg.close_figures
112 assert cfg.close_figures
113 display.set_matplotlib_close(False)
113 display.set_matplotlib_close(False)
114 assert not cfg.close_figures
114 assert not cfg.close_figures
115
115
116 _fmt_mime_map = {
116 _fmt_mime_map = {
117 'png': 'image/png',
117 'png': 'image/png',
118 'jpeg': 'image/jpeg',
118 'jpeg': 'image/jpeg',
119 'pdf': 'application/pdf',
119 'pdf': 'application/pdf',
120 'retina': 'image/png',
120 'retina': 'image/png',
121 'svg': 'image/svg+xml',
121 'svg': 'image/svg+xml',
122 }
122 }
123
123
124 @dec.skip_without('matplotlib')
124 @dec.skip_without('matplotlib')
125 def test_set_matplotlib_formats():
125 def test_set_matplotlib_formats():
126 from matplotlib.figure import Figure
126 from matplotlib.figure import Figure
127 formatters = get_ipython().display_formatter.formatters
127 formatters = get_ipython().display_formatter.formatters
128 for formats in [
128 for formats in [
129 ('png',),
129 ('png',),
130 ('pdf', 'svg'),
130 ('pdf', 'svg'),
131 ('jpeg', 'retina', 'png'),
131 ('jpeg', 'retina', 'png'),
132 (),
132 (),
133 ]:
133 ]:
134 active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
134 active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
135 display.set_matplotlib_formats(*formats)
135 display.set_matplotlib_formats(*formats)
136 for mime, f in formatters.items():
136 for mime, f in formatters.items():
137 if mime in active_mimes:
137 if mime in active_mimes:
138 nt.assert_in(Figure, f)
138 nt.assert_in(Figure, f)
139 else:
139 else:
140 nt.assert_not_in(Figure, f)
140 nt.assert_not_in(Figure, f)
141
141
142 @dec.skip_without('matplotlib')
142 @dec.skip_without('matplotlib')
143 def test_set_matplotlib_formats_kwargs():
143 def test_set_matplotlib_formats_kwargs():
144 from matplotlib.figure import Figure
144 from matplotlib.figure import Figure
145 ip = get_ipython()
145 ip = get_ipython()
146 cfg = _get_inline_config()
146 cfg = _get_inline_config()
147 cfg.print_figure_kwargs.update(dict(foo='bar'))
147 cfg.print_figure_kwargs.update(dict(foo='bar'))
148 kwargs = dict(quality=10)
148 kwargs = dict(quality=10)
149 display.set_matplotlib_formats('png', **kwargs)
149 display.set_matplotlib_formats('png', **kwargs)
150 formatter = ip.display_formatter.formatters['image/png']
150 formatter = ip.display_formatter.formatters['image/png']
151 f = formatter.lookup_by_type(Figure)
151 f = formatter.lookup_by_type(Figure)
152 cell = f.__closure__[0].cell_contents
152 cell = f.__closure__[0].cell_contents
153 expected = kwargs
153 expected = kwargs
154 expected.update(cfg.print_figure_kwargs)
154 expected.update(cfg.print_figure_kwargs)
155 nt.assert_equal(cell, expected)
155 nt.assert_equal(cell, expected)
156
156
157 def test_display_available():
157 def test_display_available():
158 """
158 """
159 Test that display is available without import
159 Test that display is available without import
160
160
161 We don't really care if it's in builtin or anything else, but it should
161 We don't really care if it's in builtin or anything else, but it should
162 always be available.
162 always be available.
163 """
163 """
164 ip = get_ipython()
164 ip = get_ipython()
165 with AssertNotPrints('NameError'):
165 with AssertNotPrints('NameError'):
166 ip.run_cell('display')
166 ip.run_cell('display')
167 try:
167 try:
168 ip.run_cell('del display')
168 ip.run_cell('del display')
169 except NameError:
169 except NameError:
170 pass # it's ok, it might be in builtins
170 pass # it's ok, it might be in builtins
171 # even if deleted it should be back
171 # even if deleted it should be back
172 with AssertNotPrints('NameError'):
172 with AssertNotPrints('NameError'):
173 ip.run_cell('display')
173 ip.run_cell('display')
174
174
175 def test_textdisplayobj_pretty_repr():
175 def test_textdisplayobj_pretty_repr():
176 p = display.Pretty("This is a simple test")
176 p = display.Pretty("This is a simple test")
177 nt.assert_equal(repr(p), '<IPython.core.display.Pretty object>')
177 nt.assert_equal(repr(p), '<IPython.core.display.Pretty object>')
178 nt.assert_equal(p.data, 'This is a simple test')
178 nt.assert_equal(p.data, 'This is a simple test')
179
179
180 p._show_mem_addr = True
180 p._show_mem_addr = True
181 nt.assert_equal(repr(p), object.__repr__(p))
181 nt.assert_equal(repr(p), object.__repr__(p))
182
182
183 def test_displayobject_repr():
183 def test_displayobject_repr():
184 h = display.HTML('<br />')
184 h = display.HTML('<br />')
185 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
185 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
186 h._show_mem_addr = True
186 h._show_mem_addr = True
187 nt.assert_equal(repr(h), object.__repr__(h))
187 nt.assert_equal(repr(h), object.__repr__(h))
188 h._show_mem_addr = False
188 h._show_mem_addr = False
189 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
189 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
190
190
191 j = display.Javascript('')
191 j = display.Javascript('')
192 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
192 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
193 j._show_mem_addr = True
193 j._show_mem_addr = True
194 nt.assert_equal(repr(j), object.__repr__(j))
194 nt.assert_equal(repr(j), object.__repr__(j))
195 j._show_mem_addr = False
195 j._show_mem_addr = False
196 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
196 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
197
197
198 @mock.patch('warnings.warn')
198 @mock.patch('warnings.warn')
199 def test_encourage_iframe_over_html(m_warn):
199 def test_encourage_iframe_over_html(m_warn):
200 display.HTML()
200 display.HTML()
201 m_warn.assert_not_called()
201 m_warn.assert_not_called()
202
202
203 display.HTML('<br />')
203 display.HTML('<br />')
204 m_warn.assert_not_called()
204 m_warn.assert_not_called()
205
205
206 display.HTML('<html><p>Lots of content here</p><iframe src="http://a.com"></iframe>')
206 display.HTML('<html><p>Lots of content here</p><iframe src="http://a.com"></iframe>')
207 m_warn.assert_not_called()
207 m_warn.assert_not_called()
208
208
209 display.HTML('<iframe src="http://a.com"></iframe>')
209 display.HTML('<iframe src="http://a.com"></iframe>')
210 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
210 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
211
211
212 m_warn.reset_mock()
212 m_warn.reset_mock()
213 display.HTML('<IFRAME SRC="http://a.com"></IFRAME>')
213 display.HTML('<IFRAME SRC="http://a.com"></IFRAME>')
214 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
214 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
215
215
216 def test_progress():
216 def test_progress():
217 p = display.ProgressBar(10)
217 p = display.ProgressBar(10)
218 nt.assert_in('0/10',repr(p))
218 nt.assert_in('0/10',repr(p))
219 p.html_width = '100%'
219 p.html_width = '100%'
220 p.progress = 5
220 p.progress = 5
221 nt.assert_equal(p._repr_html_(), "<progress style='width:100%' max='10' value='5'></progress>")
221 nt.assert_equal(p._repr_html_(), "<progress style='width:100%' max='10' value='5'></progress>")
222
222
223 def test_progress_iter():
223 def test_progress_iter():
224 with capture_output(display=False) as captured:
224 with capture_output(display=False) as captured:
225 for i in display.ProgressBar(5):
225 for i in display.ProgressBar(5):
226 out = captured.stdout
226 out = captured.stdout
227 nt.assert_in('{0}/5'.format(i), out)
227 nt.assert_in('{0}/5'.format(i), out)
228 out = captured.stdout
228 out = captured.stdout
229 nt.assert_in('5/5', out)
229 nt.assert_in('5/5', out)
230
230
231 def test_json():
231 def test_json():
232 d = {'a': 5}
232 d = {'a': 5}
233 lis = [d]
233 lis = [d]
234 metadata = [
234 metadata = [
235 {'expanded': False, 'root': 'root'},
235 {'expanded': False, 'root': 'root'},
236 {'expanded': True, 'root': 'root'},
236 {'expanded': True, 'root': 'root'},
237 {'expanded': False, 'root': 'custom'},
237 {'expanded': False, 'root': 'custom'},
238 {'expanded': True, 'root': 'custom'},
238 {'expanded': True, 'root': 'custom'},
239 ]
239 ]
240 json_objs = [
240 json_objs = [
241 display.JSON(d),
241 display.JSON(d),
242 display.JSON(d, expanded=True),
242 display.JSON(d, expanded=True),
243 display.JSON(d, root='custom'),
243 display.JSON(d, root='custom'),
244 display.JSON(d, expanded=True, root='custom'),
244 display.JSON(d, expanded=True, root='custom'),
245 ]
245 ]
246 for j, md in zip(json_objs, metadata):
246 for j, md in zip(json_objs, metadata):
247 nt.assert_equal(j._repr_json_(), (d, md))
247 nt.assert_equal(j._repr_json_(), (d, md))
248
248
249 with warnings.catch_warnings(record=True) as w:
249 with warnings.catch_warnings(record=True) as w:
250 warnings.simplefilter("always")
250 warnings.simplefilter("always")
251 j = display.JSON(json.dumps(d))
251 j = display.JSON(json.dumps(d))
252 nt.assert_equal(len(w), 1)
252 nt.assert_equal(len(w), 1)
253 nt.assert_equal(j._repr_json_(), (d, metadata[0]))
253 nt.assert_equal(j._repr_json_(), (d, metadata[0]))
254
254
255 json_objs = [
255 json_objs = [
256 display.JSON(lis),
256 display.JSON(lis),
257 display.JSON(lis, expanded=True),
257 display.JSON(lis, expanded=True),
258 display.JSON(lis, root='custom'),
258 display.JSON(lis, root='custom'),
259 display.JSON(lis, expanded=True, root='custom'),
259 display.JSON(lis, expanded=True, root='custom'),
260 ]
260 ]
261 for j, md in zip(json_objs, metadata):
261 for j, md in zip(json_objs, metadata):
262 nt.assert_equal(j._repr_json_(), (lis, md))
262 nt.assert_equal(j._repr_json_(), (lis, md))
263
263
264 with warnings.catch_warnings(record=True) as w:
264 with warnings.catch_warnings(record=True) as w:
265 warnings.simplefilter("always")
265 warnings.simplefilter("always")
266 j = display.JSON(json.dumps(lis))
266 j = display.JSON(json.dumps(lis))
267 nt.assert_equal(len(w), 1)
267 nt.assert_equal(len(w), 1)
268 nt.assert_equal(j._repr_json_(), (lis, metadata[0]))
268 nt.assert_equal(j._repr_json_(), (lis, metadata[0]))
269
269
270 def test_video_embedding():
270 def test_video_embedding():
271 """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
271 """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
272 v = display.Video("http://ignored")
272 v = display.Video("http://ignored")
273 assert not v.embed
273 assert not v.embed
274 html = v._repr_html_()
274 html = v._repr_html_()
275 nt.assert_not_in('src="data:', html)
275 nt.assert_not_in('src="data:', html)
276 nt.assert_in('src="http://ignored"', html)
276 nt.assert_in('src="http://ignored"', html)
277
277
278 with nt.assert_raises(ValueError):
278 with nt.assert_raises(ValueError):
279 v = display.Video(b'abc')
279 v = display.Video(b'abc')
280
280
281 with NamedFileInTemporaryDirectory('test.mp4') as f:
281 with NamedFileInTemporaryDirectory('test.mp4') as f:
282 f.write(b'abc')
282 f.write(b'abc')
283 f.close()
283 f.close()
284
284
285 v = display.Video(f.name)
285 v = display.Video(f.name)
286 assert not v.embed
286 assert not v.embed
287 html = v._repr_html_()
287 html = v._repr_html_()
288 nt.assert_not_in('src="data:', html)
288 nt.assert_not_in('src="data:', html)
289
289
290 v = display.Video(f.name, embed=True)
290 v = display.Video(f.name, embed=True)
291 html = v._repr_html_()
291 html = v._repr_html_()
292 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
292 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
293
293
294 v = display.Video(f.name, embed=True, mimetype='video/other')
294 v = display.Video(f.name, embed=True, mimetype='video/other')
295 html = v._repr_html_()
295 html = v._repr_html_()
296 nt.assert_in('src="data:video/other;base64,YWJj"',html)
296 nt.assert_in('src="data:video/other;base64,YWJj"',html)
297
297
298 v = display.Video(b'abc', embed=True, mimetype='video/mp4')
298 v = display.Video(b'abc', embed=True, mimetype='video/mp4')
299 html = v._repr_html_()
299 html = v._repr_html_()
300 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
300 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
301
301
302 v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
302 v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
303 html = v._repr_html_()
303 html = v._repr_html_()
304 nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
304 nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
305
305
306 def test_html_metadata():
306 def test_html_metadata():
307 s = "<h1>Test</h1>"
307 s = "<h1>Test</h1>"
308 h = display.HTML(s, metadata={"isolated": True})
308 h = display.HTML(s, metadata={"isolated": True})
309 nt.assert_equal(h._repr_html_(), (s, {"isolated": True}))
309 nt.assert_equal(h._repr_html_(), (s, {"isolated": True}))
310
310
311 def test_display_id():
311 def test_display_id():
312 ip = get_ipython()
312 ip = get_ipython()
313 with mock.patch.object(ip.display_pub, 'publish') as pub:
313 with mock.patch.object(ip.display_pub, 'publish') as pub:
314 handle = display.display('x')
314 handle = display.display('x')
315 nt.assert_is(handle, None)
315 nt.assert_is(handle, None)
316 handle = display.display('y', display_id='secret')
316 handle = display.display('y', display_id='secret')
317 nt.assert_is_instance(handle, display.DisplayHandle)
317 nt.assert_is_instance(handle, display.DisplayHandle)
318 handle2 = display.display('z', display_id=True)
318 handle2 = display.display('z', display_id=True)
319 nt.assert_is_instance(handle2, display.DisplayHandle)
319 nt.assert_is_instance(handle2, display.DisplayHandle)
320 nt.assert_not_equal(handle.display_id, handle2.display_id)
320 nt.assert_not_equal(handle.display_id, handle2.display_id)
321
321
322 nt.assert_equal(pub.call_count, 3)
322 nt.assert_equal(pub.call_count, 3)
323 args, kwargs = pub.call_args_list[0]
323 args, kwargs = pub.call_args_list[0]
324 nt.assert_equal(args, ())
324 nt.assert_equal(args, ())
325 nt.assert_equal(kwargs, {
325 nt.assert_equal(kwargs, {
326 'data': {
326 'data': {
327 'text/plain': repr('x')
327 'text/plain': repr('x')
328 },
328 },
329 'metadata': {},
329 'metadata': {},
330 })
330 })
331 args, kwargs = pub.call_args_list[1]
331 args, kwargs = pub.call_args_list[1]
332 nt.assert_equal(args, ())
332 nt.assert_equal(args, ())
333 nt.assert_equal(kwargs, {
333 nt.assert_equal(kwargs, {
334 'data': {
334 'data': {
335 'text/plain': repr('y')
335 'text/plain': repr('y')
336 },
336 },
337 'metadata': {},
337 'metadata': {},
338 'transient': {
338 'transient': {
339 'display_id': handle.display_id,
339 'display_id': handle.display_id,
340 },
340 },
341 })
341 })
342 args, kwargs = pub.call_args_list[2]
342 args, kwargs = pub.call_args_list[2]
343 nt.assert_equal(args, ())
343 nt.assert_equal(args, ())
344 nt.assert_equal(kwargs, {
344 nt.assert_equal(kwargs, {
345 'data': {
345 'data': {
346 'text/plain': repr('z')
346 'text/plain': repr('z')
347 },
347 },
348 'metadata': {},
348 'metadata': {},
349 'transient': {
349 'transient': {
350 'display_id': handle2.display_id,
350 'display_id': handle2.display_id,
351 },
351 },
352 })
352 })
353
353
354
354
355 def test_update_display():
355 def test_update_display():
356 ip = get_ipython()
356 ip = get_ipython()
357 with mock.patch.object(ip.display_pub, 'publish') as pub:
357 with mock.patch.object(ip.display_pub, 'publish') as pub:
358 with nt.assert_raises(TypeError):
358 with nt.assert_raises(TypeError):
359 display.update_display('x')
359 display.update_display('x')
360 display.update_display('x', display_id='1')
360 display.update_display('x', display_id='1')
361 display.update_display('y', display_id='2')
361 display.update_display('y', display_id='2')
362 args, kwargs = pub.call_args_list[0]
362 args, kwargs = pub.call_args_list[0]
363 nt.assert_equal(args, ())
363 nt.assert_equal(args, ())
364 nt.assert_equal(kwargs, {
364 nt.assert_equal(kwargs, {
365 'data': {
365 'data': {
366 'text/plain': repr('x')
366 'text/plain': repr('x')
367 },
367 },
368 'metadata': {},
368 'metadata': {},
369 'transient': {
369 'transient': {
370 'display_id': '1',
370 'display_id': '1',
371 },
371 },
372 'update': True,
372 'update': True,
373 })
373 })
374 args, kwargs = pub.call_args_list[1]
374 args, kwargs = pub.call_args_list[1]
375 nt.assert_equal(args, ())
375 nt.assert_equal(args, ())
376 nt.assert_equal(kwargs, {
376 nt.assert_equal(kwargs, {
377 'data': {
377 'data': {
378 'text/plain': repr('y')
378 'text/plain': repr('y')
379 },
379 },
380 'metadata': {},
380 'metadata': {},
381 'transient': {
381 'transient': {
382 'display_id': '2',
382 'display_id': '2',
383 },
383 },
384 'update': True,
384 'update': True,
385 })
385 })
386
386
387
387
388 def test_display_handle():
388 def test_display_handle():
389 ip = get_ipython()
389 ip = get_ipython()
390 handle = display.DisplayHandle()
390 handle = display.DisplayHandle()
391 nt.assert_is_instance(handle.display_id, str)
391 nt.assert_is_instance(handle.display_id, str)
392 handle = display.DisplayHandle('my-id')
392 handle = display.DisplayHandle('my-id')
393 nt.assert_equal(handle.display_id, 'my-id')
393 nt.assert_equal(handle.display_id, 'my-id')
394 with mock.patch.object(ip.display_pub, 'publish') as pub:
394 with mock.patch.object(ip.display_pub, 'publish') as pub:
395 handle.display('x')
395 handle.display('x')
396 handle.update('y')
396 handle.update('y')
397
397
398 args, kwargs = pub.call_args_list[0]
398 args, kwargs = pub.call_args_list[0]
399 nt.assert_equal(args, ())
399 nt.assert_equal(args, ())
400 nt.assert_equal(kwargs, {
400 nt.assert_equal(kwargs, {
401 'data': {
401 'data': {
402 'text/plain': repr('x')
402 'text/plain': repr('x')
403 },
403 },
404 'metadata': {},
404 'metadata': {},
405 'transient': {
405 'transient': {
406 'display_id': handle.display_id,
406 'display_id': handle.display_id,
407 }
407 }
408 })
408 })
409 args, kwargs = pub.call_args_list[1]
409 args, kwargs = pub.call_args_list[1]
410 nt.assert_equal(args, ())
410 nt.assert_equal(args, ())
411 nt.assert_equal(kwargs, {
411 nt.assert_equal(kwargs, {
412 'data': {
412 'data': {
413 'text/plain': repr('y')
413 'text/plain': repr('y')
414 },
414 },
415 'metadata': {},
415 'metadata': {},
416 'transient': {
416 'transient': {
417 'display_id': handle.display_id,
417 'display_id': handle.display_id,
418 },
418 },
419 'update': True,
419 'update': True,
420 })
420 })
421
421
@@ -1,16 +1,44 b''
1 """Public API for display tools in IPython.
1 """Public API for display tools in IPython.
2 """
2 """
3
3
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 BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 # -----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 # -----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 # -----------------------------------------------------------------------------
14
14
15 from IPython.core.display import *
15 from IPython.core.display_functions import *
16 from IPython.core.display import (
17 display_pretty,
18 display_html,
19 display_markdown,
20 display_svg,
21 display_png,
22 display_jpeg,
23 display_latex,
24 display_json,
25 display_javascript,
26 display_pdf,
27 DisplayObject,
28 TextDisplayObject,
29 Pretty,
30 HTML,
31 Markdown,
32 Math,
33 Latex,
34 SVG,
35 ProgressBar,
36 JSON,
37 GeoJSON,
38 Javascript,
39 Image,
40 set_matplotlib_formats,
41 set_matplotlib_close,
42 Video,
43 )
16 from IPython.lib.display import *
44 from IPython.lib.display import *
@@ -1,457 +1,458 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest IPython -- -vvs`. In this form
11 2. With the regular nose syntax, like `iptest IPython -- -vvs`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded. Options after `--` are passed to nose.
13 plugins loaded. Options after `--` are passed to nose.
14
14
15 """
15 """
16
16
17 # Copyright (c) IPython Development Team.
17 # Copyright (c) IPython Development Team.
18 # Distributed under the terms of the Modified BSD License.
18 # Distributed under the terms of the Modified BSD License.
19
19
20
20
21 import glob
21 import glob
22 from io import BytesIO
22 from io import BytesIO
23 import os
23 import os
24 import os.path as path
24 import os.path as path
25 import sys
25 import sys
26 from threading import Thread, Lock, Event
26 from threading import Thread, Lock, Event
27 import warnings
27 import warnings
28
28
29 import nose.plugins.builtin
29 import nose.plugins.builtin
30 from nose.plugins.xunit import Xunit
30 from nose.plugins.xunit import Xunit
31 from nose import SkipTest
31 from nose import SkipTest
32 from nose.core import TestProgram
32 from nose.core import TestProgram
33 from nose.plugins import Plugin
33 from nose.plugins import Plugin
34 from nose.util import safe_str
34 from nose.util import safe_str
35
35
36 from IPython import version_info
36 from IPython import version_info
37 from IPython.utils.py3compat import decode
37 from IPython.utils.py3compat import decode
38 from IPython.utils.importstring import import_item
38 from IPython.utils.importstring import import_item
39 from IPython.testing.plugin.ipdoctest import IPythonDoctest
39 from IPython.testing.plugin.ipdoctest import IPythonDoctest
40 from IPython.external.decorators import KnownFailure, knownfailureif
40 from IPython.external.decorators import KnownFailure, knownfailureif
41
41
42 pjoin = path.join
42 pjoin = path.join
43
43
44
44
45 # Enable printing all warnings raise by IPython's modules
45 # Enable printing all warnings raise by IPython's modules
46 warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*')
46 warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*')
47 warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*')
47 warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*')
48 warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*')
48 warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*')
49 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
49 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
50
50
51 warnings.filterwarnings('error', message='.*apply_wrapper.*', category=DeprecationWarning, module='.*')
51 warnings.filterwarnings('error', message='.*apply_wrapper.*', category=DeprecationWarning, module='.*')
52 warnings.filterwarnings('error', message='.*make_label_dec', category=DeprecationWarning, module='.*')
52 warnings.filterwarnings('error', message='.*make_label_dec', category=DeprecationWarning, module='.*')
53 warnings.filterwarnings('error', message='.*decorated_dummy.*', category=DeprecationWarning, module='.*')
53 warnings.filterwarnings('error', message='.*decorated_dummy.*', category=DeprecationWarning, module='.*')
54 warnings.filterwarnings('error', message='.*skip_file_no_x11.*', category=DeprecationWarning, module='.*')
54 warnings.filterwarnings('error', message='.*skip_file_no_x11.*', category=DeprecationWarning, module='.*')
55 warnings.filterwarnings('error', message='.*onlyif_any_cmd_exists.*', category=DeprecationWarning, module='.*')
55 warnings.filterwarnings('error', message='.*onlyif_any_cmd_exists.*', category=DeprecationWarning, module='.*')
56
56
57 warnings.filterwarnings('error', message='.*disable_gui.*', category=DeprecationWarning, module='.*')
57 warnings.filterwarnings('error', message='.*disable_gui.*', category=DeprecationWarning, module='.*')
58
58
59 warnings.filterwarnings('error', message='.*ExceptionColors global is deprecated.*', category=DeprecationWarning, module='.*')
59 warnings.filterwarnings('error', message='.*ExceptionColors global is deprecated.*', category=DeprecationWarning, module='.*')
60 warnings.filterwarnings('error', message='.*IPython.core.display.*', category=DeprecationWarning, module='.*')
60
61
61 # Jedi older versions
62 # Jedi older versions
62 warnings.filterwarnings(
63 warnings.filterwarnings(
63 'error', message='.*elementwise != comparison failed and.*', category=FutureWarning, module='.*')
64 'error', message='.*elementwise != comparison failed and.*', category=FutureWarning, module='.*')
64
65
65 if version_info < (6,):
66 if version_info < (6,):
66 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
67 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
67 # warning with the runner they also import from standard import library. (as of Dec 2015)
68 # warning with the runner they also import from standard import library. (as of Dec 2015)
68 # Ignore, let's revisit that in a couple of years for IPython 6.
69 # Ignore, let's revisit that in a couple of years for IPython 6.
69 warnings.filterwarnings(
70 warnings.filterwarnings(
70 'ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
71 'ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
71
72
72 if version_info < (8,):
73 if version_info < (8,):
73 warnings.filterwarnings('ignore', message='.*Completer.complete.*',
74 warnings.filterwarnings('ignore', message='.*Completer.complete.*',
74 category=PendingDeprecationWarning, module='.*')
75 category=PendingDeprecationWarning, module='.*')
75 else:
76 else:
76 warnings.warn(
77 warnings.warn(
77 'Completer.complete was pending deprecation and should be changed to Deprecated', FutureWarning)
78 'Completer.complete was pending deprecation and should be changed to Deprecated', FutureWarning)
78
79
79
80
80
81
81 # ------------------------------------------------------------------------------
82 # ------------------------------------------------------------------------------
82 # Monkeypatch Xunit to count known failures as skipped.
83 # Monkeypatch Xunit to count known failures as skipped.
83 # ------------------------------------------------------------------------------
84 # ------------------------------------------------------------------------------
84 def monkeypatch_xunit():
85 def monkeypatch_xunit():
85 try:
86 try:
86 dec.knownfailureif(True)(lambda: None)()
87 dec.knownfailureif(True)(lambda: None)()
87 except Exception as e:
88 except Exception as e:
88 KnownFailureTest = type(e)
89 KnownFailureTest = type(e)
89
90
90 def addError(self, test, err, capt=None):
91 def addError(self, test, err, capt=None):
91 if issubclass(err[0], KnownFailureTest):
92 if issubclass(err[0], KnownFailureTest):
92 err = (SkipTest,) + err[1:]
93 err = (SkipTest,) + err[1:]
93 return self.orig_addError(test, err, capt)
94 return self.orig_addError(test, err, capt)
94
95
95 Xunit.orig_addError = Xunit.addError
96 Xunit.orig_addError = Xunit.addError
96 Xunit.addError = addError
97 Xunit.addError = addError
97
98
98 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
99 # Check which dependencies are installed and greater than minimum version.
100 # Check which dependencies are installed and greater than minimum version.
100 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
101 def extract_version(mod):
102 def extract_version(mod):
102 return mod.__version__
103 return mod.__version__
103
104
104 def test_for(item, min_version=None, callback=extract_version):
105 def test_for(item, min_version=None, callback=extract_version):
105 """Test to see if item is importable, and optionally check against a minimum
106 """Test to see if item is importable, and optionally check against a minimum
106 version.
107 version.
107
108
108 If min_version is given, the default behavior is to check against the
109 If min_version is given, the default behavior is to check against the
109 `__version__` attribute of the item, but specifying `callback` allows you to
110 `__version__` attribute of the item, but specifying `callback` allows you to
110 extract the value you are interested in. e.g::
111 extract the value you are interested in. e.g::
111
112
112 In [1]: import sys
113 In [1]: import sys
113
114
114 In [2]: from IPython.testing.iptest import test_for
115 In [2]: from IPython.testing.iptest import test_for
115
116
116 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
117 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
117 Out[3]: True
118 Out[3]: True
118
119
119 """
120 """
120 try:
121 try:
121 check = import_item(item)
122 check = import_item(item)
122 except (ImportError, RuntimeError):
123 except (ImportError, RuntimeError):
123 # GTK reports Runtime error if it can't be initialized even if it's
124 # GTK reports Runtime error if it can't be initialized even if it's
124 # importable.
125 # importable.
125 return False
126 return False
126 else:
127 else:
127 if min_version:
128 if min_version:
128 if callback:
129 if callback:
129 # extra processing step to get version to compare
130 # extra processing step to get version to compare
130 check = callback(check)
131 check = callback(check)
131
132
132 return check >= min_version
133 return check >= min_version
133 else:
134 else:
134 return True
135 return True
135
136
136 # Global dict where we can store information on what we have and what we don't
137 # Global dict where we can store information on what we have and what we don't
137 # have available at test run time
138 # have available at test run time
138 have = {'matplotlib': test_for('matplotlib'),
139 have = {'matplotlib': test_for('matplotlib'),
139 'pygments': test_for('pygments'),
140 'pygments': test_for('pygments'),
140 }
141 }
141
142
142 #-----------------------------------------------------------------------------
143 #-----------------------------------------------------------------------------
143 # Test suite definitions
144 # Test suite definitions
144 #-----------------------------------------------------------------------------
145 #-----------------------------------------------------------------------------
145
146
146 test_group_names = ['core',
147 test_group_names = ['core',
147 'extensions', 'lib', 'terminal', 'testing', 'utils',
148 'extensions', 'lib', 'terminal', 'testing', 'utils',
148 ]
149 ]
149
150
150 class TestSection(object):
151 class TestSection(object):
151 def __init__(self, name, includes):
152 def __init__(self, name, includes):
152 self.name = name
153 self.name = name
153 self.includes = includes
154 self.includes = includes
154 self.excludes = []
155 self.excludes = []
155 self.dependencies = []
156 self.dependencies = []
156 self.enabled = True
157 self.enabled = True
157
158
158 def exclude(self, module):
159 def exclude(self, module):
159 if not module.startswith('IPython'):
160 if not module.startswith('IPython'):
160 module = self.includes[0] + "." + module
161 module = self.includes[0] + "." + module
161 self.excludes.append(module.replace('.', os.sep))
162 self.excludes.append(module.replace('.', os.sep))
162
163
163 def requires(self, *packages):
164 def requires(self, *packages):
164 self.dependencies.extend(packages)
165 self.dependencies.extend(packages)
165
166
166 @property
167 @property
167 def will_run(self):
168 def will_run(self):
168 return self.enabled and all(have[p] for p in self.dependencies)
169 return self.enabled and all(have[p] for p in self.dependencies)
169
170
170 # Name -> (include, exclude, dependencies_met)
171 # Name -> (include, exclude, dependencies_met)
171 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
172 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
172
173
173
174
174 # Exclusions and dependencies
175 # Exclusions and dependencies
175 # ---------------------------
176 # ---------------------------
176
177
177 # core:
178 # core:
178 sec = test_sections['core']
179 sec = test_sections['core']
179 if not have['matplotlib']:
180 if not have['matplotlib']:
180 sec.exclude('pylabtools'),
181 sec.exclude('pylabtools'),
181 sec.exclude('tests.test_pylabtools')
182 sec.exclude('tests.test_pylabtools')
182
183
183 # lib:
184 # lib:
184 sec = test_sections['lib']
185 sec = test_sections['lib']
185 sec.exclude('kernel')
186 sec.exclude('kernel')
186 if not have['pygments']:
187 if not have['pygments']:
187 sec.exclude('tests.test_lexers')
188 sec.exclude('tests.test_lexers')
188 # We do this unconditionally, so that the test suite doesn't import
189 # We do this unconditionally, so that the test suite doesn't import
189 # gtk, changing the default encoding and masking some unicode bugs.
190 # gtk, changing the default encoding and masking some unicode bugs.
190 sec.exclude('inputhookgtk')
191 sec.exclude('inputhookgtk')
191 # We also do this unconditionally, because wx can interfere with Unix signals.
192 # We also do this unconditionally, because wx can interfere with Unix signals.
192 # There are currently no tests for it anyway.
193 # There are currently no tests for it anyway.
193 sec.exclude('inputhookwx')
194 sec.exclude('inputhookwx')
194 # Testing inputhook will need a lot of thought, to figure out
195 # Testing inputhook will need a lot of thought, to figure out
195 # how to have tests that don't lock up with the gui event
196 # how to have tests that don't lock up with the gui event
196 # loops in the picture
197 # loops in the picture
197 sec.exclude('inputhook')
198 sec.exclude('inputhook')
198
199
199 # testing:
200 # testing:
200 sec = test_sections['testing']
201 sec = test_sections['testing']
201 # These have to be skipped on win32 because they use echo, rm, cd, etc.
202 # These have to be skipped on win32 because they use echo, rm, cd, etc.
202 # See ticket https://github.com/ipython/ipython/issues/87
203 # See ticket https://github.com/ipython/ipython/issues/87
203 if sys.platform == 'win32':
204 if sys.platform == 'win32':
204 sec.exclude('plugin.test_exampleip')
205 sec.exclude('plugin.test_exampleip')
205 sec.exclude('plugin.dtexample')
206 sec.exclude('plugin.dtexample')
206
207
207 # don't run jupyter_console tests found via shim
208 # don't run jupyter_console tests found via shim
208 test_sections['terminal'].exclude('console')
209 test_sections['terminal'].exclude('console')
209
210
210 # extensions:
211 # extensions:
211 sec = test_sections['extensions']
212 sec = test_sections['extensions']
212 # This is deprecated in favour of rpy2
213 # This is deprecated in favour of rpy2
213 sec.exclude('rmagic')
214 sec.exclude('rmagic')
214 # autoreload does some strange stuff, so move it to its own test section
215 # autoreload does some strange stuff, so move it to its own test section
215 sec.exclude('autoreload')
216 sec.exclude('autoreload')
216 sec.exclude('tests.test_autoreload')
217 sec.exclude('tests.test_autoreload')
217 test_sections['autoreload'] = TestSection('autoreload',
218 test_sections['autoreload'] = TestSection('autoreload',
218 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
219 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
219 test_group_names.append('autoreload')
220 test_group_names.append('autoreload')
220
221
221
222
222 #-----------------------------------------------------------------------------
223 #-----------------------------------------------------------------------------
223 # Functions and classes
224 # Functions and classes
224 #-----------------------------------------------------------------------------
225 #-----------------------------------------------------------------------------
225
226
226 def check_exclusions_exist():
227 def check_exclusions_exist():
227 from IPython.paths import get_ipython_package_dir
228 from IPython.paths import get_ipython_package_dir
228 from warnings import warn
229 from warnings import warn
229 parent = os.path.dirname(get_ipython_package_dir())
230 parent = os.path.dirname(get_ipython_package_dir())
230 for sec in test_sections:
231 for sec in test_sections:
231 for pattern in sec.exclusions:
232 for pattern in sec.exclusions:
232 fullpath = pjoin(parent, pattern)
233 fullpath = pjoin(parent, pattern)
233 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
234 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
234 warn("Excluding nonexistent file: %r" % pattern)
235 warn("Excluding nonexistent file: %r" % pattern)
235
236
236
237
237 class ExclusionPlugin(Plugin):
238 class ExclusionPlugin(Plugin):
238 """A nose plugin to effect our exclusions of files and directories.
239 """A nose plugin to effect our exclusions of files and directories.
239 """
240 """
240 name = 'exclusions'
241 name = 'exclusions'
241 score = 3000 # Should come before any other plugins
242 score = 3000 # Should come before any other plugins
242
243
243 def __init__(self, exclude_patterns=None):
244 def __init__(self, exclude_patterns=None):
244 """
245 """
245 Parameters
246 Parameters
246 ----------
247 ----------
247
248
248 exclude_patterns : sequence of strings, optional
249 exclude_patterns : sequence of strings, optional
249 Filenames containing these patterns (as raw strings, not as regular
250 Filenames containing these patterns (as raw strings, not as regular
250 expressions) are excluded from the tests.
251 expressions) are excluded from the tests.
251 """
252 """
252 self.exclude_patterns = exclude_patterns or []
253 self.exclude_patterns = exclude_patterns or []
253 super(ExclusionPlugin, self).__init__()
254 super(ExclusionPlugin, self).__init__()
254
255
255 def options(self, parser, env=os.environ):
256 def options(self, parser, env=os.environ):
256 Plugin.options(self, parser, env)
257 Plugin.options(self, parser, env)
257
258
258 def configure(self, options, config):
259 def configure(self, options, config):
259 Plugin.configure(self, options, config)
260 Plugin.configure(self, options, config)
260 # Override nose trying to disable plugin.
261 # Override nose trying to disable plugin.
261 self.enabled = True
262 self.enabled = True
262
263
263 def wantFile(self, filename):
264 def wantFile(self, filename):
264 """Return whether the given filename should be scanned for tests.
265 """Return whether the given filename should be scanned for tests.
265 """
266 """
266 if any(pat in filename for pat in self.exclude_patterns):
267 if any(pat in filename for pat in self.exclude_patterns):
267 return False
268 return False
268 return None
269 return None
269
270
270 def wantDirectory(self, directory):
271 def wantDirectory(self, directory):
271 """Return whether the given directory should be scanned for tests.
272 """Return whether the given directory should be scanned for tests.
272 """
273 """
273 if any(pat in directory for pat in self.exclude_patterns):
274 if any(pat in directory for pat in self.exclude_patterns):
274 return False
275 return False
275 return None
276 return None
276
277
277
278
278 class StreamCapturer(Thread):
279 class StreamCapturer(Thread):
279 daemon = True # Don't hang if main thread crashes
280 daemon = True # Don't hang if main thread crashes
280 started = False
281 started = False
281 def __init__(self, echo=False):
282 def __init__(self, echo=False):
282 super(StreamCapturer, self).__init__()
283 super(StreamCapturer, self).__init__()
283 self.echo = echo
284 self.echo = echo
284 self.streams = []
285 self.streams = []
285 self.buffer = BytesIO()
286 self.buffer = BytesIO()
286 self.readfd, self.writefd = os.pipe()
287 self.readfd, self.writefd = os.pipe()
287 self.buffer_lock = Lock()
288 self.buffer_lock = Lock()
288 self.stop = Event()
289 self.stop = Event()
289
290
290 def run(self):
291 def run(self):
291 self.started = True
292 self.started = True
292
293
293 while not self.stop.is_set():
294 while not self.stop.is_set():
294 chunk = os.read(self.readfd, 1024)
295 chunk = os.read(self.readfd, 1024)
295
296
296 with self.buffer_lock:
297 with self.buffer_lock:
297 self.buffer.write(chunk)
298 self.buffer.write(chunk)
298 if self.echo:
299 if self.echo:
299 sys.stdout.write(decode(chunk))
300 sys.stdout.write(decode(chunk))
300
301
301 os.close(self.readfd)
302 os.close(self.readfd)
302 os.close(self.writefd)
303 os.close(self.writefd)
303
304
304 def reset_buffer(self):
305 def reset_buffer(self):
305 with self.buffer_lock:
306 with self.buffer_lock:
306 self.buffer.truncate(0)
307 self.buffer.truncate(0)
307 self.buffer.seek(0)
308 self.buffer.seek(0)
308
309
309 def get_buffer(self):
310 def get_buffer(self):
310 with self.buffer_lock:
311 with self.buffer_lock:
311 return self.buffer.getvalue()
312 return self.buffer.getvalue()
312
313
313 def ensure_started(self):
314 def ensure_started(self):
314 if not self.started:
315 if not self.started:
315 self.start()
316 self.start()
316
317
317 def halt(self):
318 def halt(self):
318 """Safely stop the thread."""
319 """Safely stop the thread."""
319 if not self.started:
320 if not self.started:
320 return
321 return
321
322
322 self.stop.set()
323 self.stop.set()
323 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
324 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
324 self.join()
325 self.join()
325
326
326 class SubprocessStreamCapturePlugin(Plugin):
327 class SubprocessStreamCapturePlugin(Plugin):
327 name='subprocstreams'
328 name='subprocstreams'
328 def __init__(self):
329 def __init__(self):
329 Plugin.__init__(self)
330 Plugin.__init__(self)
330 self.stream_capturer = StreamCapturer()
331 self.stream_capturer = StreamCapturer()
331 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
332 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
332 # This is ugly, but distant parts of the test machinery need to be able
333 # This is ugly, but distant parts of the test machinery need to be able
333 # to redirect streams, so we make the object globally accessible.
334 # to redirect streams, so we make the object globally accessible.
334 nose.iptest_stdstreams_fileno = self.get_write_fileno
335 nose.iptest_stdstreams_fileno = self.get_write_fileno
335
336
336 def get_write_fileno(self):
337 def get_write_fileno(self):
337 if self.destination == 'capture':
338 if self.destination == 'capture':
338 self.stream_capturer.ensure_started()
339 self.stream_capturer.ensure_started()
339 return self.stream_capturer.writefd
340 return self.stream_capturer.writefd
340 elif self.destination == 'discard':
341 elif self.destination == 'discard':
341 return os.open(os.devnull, os.O_WRONLY)
342 return os.open(os.devnull, os.O_WRONLY)
342 else:
343 else:
343 return sys.__stdout__.fileno()
344 return sys.__stdout__.fileno()
344
345
345 def configure(self, options, config):
346 def configure(self, options, config):
346 Plugin.configure(self, options, config)
347 Plugin.configure(self, options, config)
347 # Override nose trying to disable plugin.
348 # Override nose trying to disable plugin.
348 if self.destination == 'capture':
349 if self.destination == 'capture':
349 self.enabled = True
350 self.enabled = True
350
351
351 def startTest(self, test):
352 def startTest(self, test):
352 # Reset log capture
353 # Reset log capture
353 self.stream_capturer.reset_buffer()
354 self.stream_capturer.reset_buffer()
354
355
355 def formatFailure(self, test, err):
356 def formatFailure(self, test, err):
356 # Show output
357 # Show output
357 ec, ev, tb = err
358 ec, ev, tb = err
358 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
359 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
359 if captured.strip():
360 if captured.strip():
360 ev = safe_str(ev)
361 ev = safe_str(ev)
361 out = [ev, '>> begin captured subprocess output <<',
362 out = [ev, '>> begin captured subprocess output <<',
362 captured,
363 captured,
363 '>> end captured subprocess output <<']
364 '>> end captured subprocess output <<']
364 return ec, '\n'.join(out), tb
365 return ec, '\n'.join(out), tb
365
366
366 return err
367 return err
367
368
368 formatError = formatFailure
369 formatError = formatFailure
369
370
370 def finalize(self, result):
371 def finalize(self, result):
371 self.stream_capturer.halt()
372 self.stream_capturer.halt()
372
373
373
374
374 def run_iptest():
375 def run_iptest():
375 """Run the IPython test suite using nose.
376 """Run the IPython test suite using nose.
376
377
377 This function is called when this script is **not** called with the form
378 This function is called when this script is **not** called with the form
378 `iptest all`. It simply calls nose with appropriate command line flags
379 `iptest all`. It simply calls nose with appropriate command line flags
379 and accepts all of the standard nose arguments.
380 and accepts all of the standard nose arguments.
380 """
381 """
381 # Apply our monkeypatch to Xunit
382 # Apply our monkeypatch to Xunit
382 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
383 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
383 monkeypatch_xunit()
384 monkeypatch_xunit()
384
385
385 arg1 = sys.argv[1]
386 arg1 = sys.argv[1]
386 if arg1.startswith('IPython/'):
387 if arg1.startswith('IPython/'):
387 if arg1.endswith('.py'):
388 if arg1.endswith('.py'):
388 arg1 = arg1[:-3]
389 arg1 = arg1[:-3]
389 sys.argv[1] = arg1.replace('/', '.')
390 sys.argv[1] = arg1.replace('/', '.')
390
391
391 arg1 = sys.argv[1]
392 arg1 = sys.argv[1]
392 if arg1 in test_sections:
393 if arg1 in test_sections:
393 section = test_sections[arg1]
394 section = test_sections[arg1]
394 sys.argv[1:2] = section.includes
395 sys.argv[1:2] = section.includes
395 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
396 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
396 section = test_sections[arg1[8:]]
397 section = test_sections[arg1[8:]]
397 sys.argv[1:2] = section.includes
398 sys.argv[1:2] = section.includes
398 else:
399 else:
399 section = TestSection(arg1, includes=[arg1])
400 section = TestSection(arg1, includes=[arg1])
400
401
401
402
402 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
403 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
403 # We add --exe because of setuptools' imbecility (it
404 # We add --exe because of setuptools' imbecility (it
404 # blindly does chmod +x on ALL files). Nose does the
405 # blindly does chmod +x on ALL files). Nose does the
405 # right thing and it tries to avoid executables,
406 # right thing and it tries to avoid executables,
406 # setuptools unfortunately forces our hand here. This
407 # setuptools unfortunately forces our hand here. This
407 # has been discussed on the distutils list and the
408 # has been discussed on the distutils list and the
408 # setuptools devs refuse to fix this problem!
409 # setuptools devs refuse to fix this problem!
409 '--exe',
410 '--exe',
410 ]
411 ]
411 if '-a' not in argv and '-A' not in argv:
412 if '-a' not in argv and '-A' not in argv:
412 argv = argv + ['-a', '!crash']
413 argv = argv + ['-a', '!crash']
413
414
414 if nose.__version__ >= '0.11':
415 if nose.__version__ >= '0.11':
415 # I don't fully understand why we need this one, but depending on what
416 # I don't fully understand why we need this one, but depending on what
416 # directory the test suite is run from, if we don't give it, 0 tests
417 # directory the test suite is run from, if we don't give it, 0 tests
417 # get run. Specifically, if the test suite is run from the source dir
418 # get run. Specifically, if the test suite is run from the source dir
418 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
419 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
419 # even if the same call done in this directory works fine). It appears
420 # even if the same call done in this directory works fine). It appears
420 # that if the requested package is in the current dir, nose bails early
421 # that if the requested package is in the current dir, nose bails early
421 # by default. Since it's otherwise harmless, leave it in by default
422 # by default. Since it's otherwise harmless, leave it in by default
422 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
423 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
423 argv.append('--traverse-namespace')
424 argv.append('--traverse-namespace')
424
425
425 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
426 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
426 SubprocessStreamCapturePlugin() ]
427 SubprocessStreamCapturePlugin() ]
427
428
428 # we still have some vestigial doctests in core
429 # we still have some vestigial doctests in core
429 if (section.name.startswith(('core', 'IPython.core', 'IPython.utils'))):
430 if (section.name.startswith(('core', 'IPython.core', 'IPython.utils'))):
430 plugins.append(IPythonDoctest())
431 plugins.append(IPythonDoctest())
431 argv.extend([
432 argv.extend([
432 '--with-ipdoctest',
433 '--with-ipdoctest',
433 '--ipdoctest-tests',
434 '--ipdoctest-tests',
434 '--ipdoctest-extension=txt',
435 '--ipdoctest-extension=txt',
435 ])
436 ])
436
437
437
438
438 # Use working directory set by parent process (see iptestcontroller)
439 # Use working directory set by parent process (see iptestcontroller)
439 if 'IPTEST_WORKING_DIR' in os.environ:
440 if 'IPTEST_WORKING_DIR' in os.environ:
440 os.chdir(os.environ['IPTEST_WORKING_DIR'])
441 os.chdir(os.environ['IPTEST_WORKING_DIR'])
441
442
442 # We need a global ipython running in this process, but the special
443 # We need a global ipython running in this process, but the special
443 # in-process group spawns its own IPython kernels, so for *that* group we
444 # in-process group spawns its own IPython kernels, so for *that* group we
444 # must avoid also opening the global one (otherwise there's a conflict of
445 # must avoid also opening the global one (otherwise there's a conflict of
445 # singletons). Ultimately the solution to this problem is to refactor our
446 # singletons). Ultimately the solution to this problem is to refactor our
446 # assumptions about what needs to be a singleton and what doesn't (app
447 # assumptions about what needs to be a singleton and what doesn't (app
447 # objects should, individual shells shouldn't). But for now, this
448 # objects should, individual shells shouldn't). But for now, this
448 # workaround allows the test suite for the inprocess module to complete.
449 # workaround allows the test suite for the inprocess module to complete.
449 if 'kernel.inprocess' not in section.name:
450 if 'kernel.inprocess' not in section.name:
450 from IPython.testing import globalipapp
451 from IPython.testing import globalipapp
451 globalipapp.start_ipython()
452 globalipapp.start_ipython()
452
453
453 # Now nose can run
454 # Now nose can run
454 TestProgram(argv=argv, addplugins=plugins)
455 TestProgram(argv=argv, addplugins=plugins)
455
456
456 if __name__ == '__main__':
457 if __name__ == '__main__':
457 run_iptest()
458 run_iptest()
General Comments 0
You need to be logged in to leave comments. Login now