##// END OF EJS Templates
Merge pull request #12239 from martinRenou/refactor_display...
Matthias Bussonnier -
r25683:e2ebf249 merge
parent child Browse files
Show More
@@ -0,0 +1,367 b''
1 # -*- coding: utf-8 -*-
2 """Top-level display functions for displaying object in different formats."""
3
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
6
7
8 from binascii import b2a_hex
9 import os
10 import sys
11
12 __all__ = ['display', 'clear_output', 'publish_display_data', 'update_display', 'DisplayHandle']
13
14 #-----------------------------------------------------------------------------
15 # utility functions
16 #-----------------------------------------------------------------------------
17
18
19 def _merge(d1, d2):
20 """Like update, but merges sub-dicts instead of clobbering at the top level.
21
22 Updates d1 in-place
23 """
24
25 if not isinstance(d2, dict) or not isinstance(d1, dict):
26 return d2
27 for key, value in d2.items():
28 d1[key] = _merge(d1.get(key), value)
29 return d1
30
31
32 #-----------------------------------------------------------------------------
33 # Main functions
34 #-----------------------------------------------------------------------------
35
36
37 # use * to indicate transient is keyword-only
38 def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs):
39 """Publish data and metadata to all frontends.
40
41 See the ``display_data`` message in the messaging documentation for
42 more details about this message type.
43
44 Keys of data and metadata can be any mime-type.
45
46 Parameters
47 ----------
48 data : dict
49 A dictionary having keys that are valid MIME types (like
50 'text/plain' or 'image/svg+xml') and values that are the data for
51 that MIME type. The data itself must be a JSON'able data
52 structure. Minimally all data should have the 'text/plain' data,
53 which can be displayed by all frontends. If more than the plain
54 text is given, it is up to the frontend to decide which
55 representation to use.
56 metadata : dict
57 A dictionary for metadata related to the data. This can contain
58 arbitrary key, value pairs that frontends can use to interpret
59 the data. mime-type keys matching those in data can be used
60 to specify metadata about particular representations.
61 source : str, deprecated
62 Unused.
63 transient : dict, keyword-only
64 A dictionary of transient data, such as display_id.
65 """
66 from IPython.core.interactiveshell import InteractiveShell
67
68 display_pub = InteractiveShell.instance().display_pub
69
70 # only pass transient if supplied,
71 # to avoid errors with older ipykernel.
72 # TODO: We could check for ipykernel version and provide a detailed upgrade message.
73 if transient:
74 kwargs['transient'] = transient
75
76 display_pub.publish(
77 data=data,
78 metadata=metadata,
79 **kwargs
80 )
81
82
83 def _new_id():
84 """Generate a new random text id with urandom"""
85 return b2a_hex(os.urandom(16)).decode('ascii')
86
87
88 def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs):
89 """Display a Python object in all frontends.
90
91 By default all representations will be computed and sent to the frontends.
92 Frontends can decide which representation is used and how.
93
94 In terminal IPython this will be similar to using :func:`print`, for use in richer
95 frontends see Jupyter notebook examples with rich display logic.
96
97 Parameters
98 ----------
99 *objs : object
100 The Python objects to display.
101 raw : bool, optional
102 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
103 or Python objects that need to be formatted before display? [default: False]
104 include : list, tuple or set, optional
105 A list of format type strings (MIME types) to include in the
106 format data dict. If this is set *only* the format types included
107 in this list will be computed.
108 exclude : list, tuple or set, optional
109 A list of format type strings (MIME types) to exclude in the format
110 data dict. If this is set all format types will be computed,
111 except for those included in this argument.
112 metadata : dict, optional
113 A dictionary of metadata to associate with the output.
114 mime-type keys in this dictionary will be associated with the individual
115 representation formats, if they exist.
116 transient : dict, optional
117 A dictionary of transient data to associate with the output.
118 Data in this dict should not be persisted to files (e.g. notebooks).
119 display_id : str, bool optional
120 Set an id for the display.
121 This id can be used for updating this display area later via update_display.
122 If given as `True`, generate a new `display_id`
123 kwargs: additional keyword-args, optional
124 Additional keyword-arguments are passed through to the display publisher.
125
126 Returns
127 -------
128
129 handle: DisplayHandle
130 Returns a handle on updatable displays for use with :func:`update_display`,
131 if `display_id` is given. Returns :any:`None` if no `display_id` is given
132 (default).
133
134 Examples
135 --------
136
137 >>> class Json(object):
138 ... def __init__(self, json):
139 ... self.json = json
140 ... def _repr_pretty_(self, pp, cycle):
141 ... import json
142 ... pp.text(json.dumps(self.json, indent=2))
143 ... def __repr__(self):
144 ... return str(self.json)
145 ...
146
147 >>> d = Json({1:2, 3: {4:5}})
148
149 >>> print(d)
150 {1: 2, 3: {4: 5}}
151
152 >>> display(d)
153 {
154 "1": 2,
155 "3": {
156 "4": 5
157 }
158 }
159
160 >>> def int_formatter(integer, pp, cycle):
161 ... pp.text('I'*integer)
162
163 >>> plain = get_ipython().display_formatter.formatters['text/plain']
164 >>> plain.for_type(int, int_formatter)
165 <function _repr_pprint at 0x...>
166 >>> display(7-5)
167 II
168
169 >>> del plain.type_printers[int]
170 >>> display(7-5)
171 2
172
173 See Also
174 --------
175
176 :func:`update_display`
177
178 Notes
179 -----
180
181 In Python, objects can declare their textual representation using the
182 `__repr__` method. IPython expands on this idea and allows objects to declare
183 other, rich representations including:
184
185 - HTML
186 - JSON
187 - PNG
188 - JPEG
189 - SVG
190 - LaTeX
191
192 A single object can declare some or all of these representations; all are
193 handled by IPython's display system.
194
195 The main idea of the first approach is that you have to implement special
196 display methods when you define your class, one for each representation you
197 want to use. Here is a list of the names of the special methods and the
198 values they must return:
199
200 - `_repr_html_`: return raw HTML as a string, or a tuple (see below).
201 - `_repr_json_`: return a JSONable dict, or a tuple (see below).
202 - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below).
203 - `_repr_png_`: return raw PNG data, or a tuple (see below).
204 - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below).
205 - `_repr_latex_`: return LaTeX commands in a string surrounded by "$",
206 or a tuple (see below).
207 - `_repr_mimebundle_`: return a full mimebundle containing the mapping
208 from all mimetypes to data.
209 Use this for any mime-type not listed above.
210
211 The above functions may also return the object's metadata alonside the
212 data. If the metadata is available, the functions will return a tuple
213 containing the data and metadata, in that order. If there is no metadata
214 available, then the functions will return the data only.
215
216 When you are directly writing your own classes, you can adapt them for
217 display in IPython by following the above approach. But in practice, you
218 often need to work with existing classes that you can't easily modify.
219
220 You can refer to the documentation on integrating with the display system in
221 order to register custom formatters for already existing types
222 (:ref:`integrating_rich_display`).
223
224 .. versionadded:: 5.4 display available without import
225 .. versionadded:: 6.1 display available without import
226
227 Since IPython 5.4 and 6.1 :func:`display` is automatically made available to
228 the user without import. If you are using display in a document that might
229 be used in a pure python context or with older version of IPython, use the
230 following import at the top of your file::
231
232 from IPython.display import display
233
234 """
235 from IPython.core.interactiveshell import InteractiveShell
236
237 if not InteractiveShell.initialized():
238 # Directly print objects.
239 print(*objs)
240 return
241
242 raw = kwargs.pop('raw', False)
243 if transient is None:
244 transient = {}
245 if metadata is None:
246 metadata={}
247 if display_id:
248 if display_id is True:
249 display_id = _new_id()
250 transient['display_id'] = display_id
251 if kwargs.get('update') and 'display_id' not in transient:
252 raise TypeError('display_id required for update_display')
253 if transient:
254 kwargs['transient'] = transient
255
256 if not objs and display_id:
257 # if given no objects, but still a request for a display_id,
258 # we assume the user wants to insert an empty output that
259 # can be updated later
260 objs = [{}]
261 raw = True
262
263 if not raw:
264 format = InteractiveShell.instance().display_formatter.format
265
266 for obj in objs:
267 if raw:
268 publish_display_data(data=obj, metadata=metadata, **kwargs)
269 else:
270 format_dict, md_dict = format(obj, include=include, exclude=exclude)
271 if not format_dict:
272 # nothing to display (e.g. _ipython_display_ took over)
273 continue
274 if metadata:
275 # kwarg-specified metadata gets precedence
276 _merge(md_dict, metadata)
277 publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
278 if display_id:
279 return DisplayHandle(display_id)
280
281
282 # use * for keyword-only display_id arg
283 def update_display(obj, *, display_id, **kwargs):
284 """Update an existing display by id
285
286 Parameters
287 ----------
288
289 obj:
290 The object with which to update the display
291 display_id: keyword-only
292 The id of the display to update
293
294 See Also
295 --------
296
297 :func:`display`
298 """
299 kwargs['update'] = True
300 display(obj, display_id=display_id, **kwargs)
301
302
303 class DisplayHandle(object):
304 """A handle on an updatable display
305
306 Call `.update(obj)` to display a new object.
307
308 Call `.display(obj`) to add a new instance of this display,
309 and update existing instances.
310
311 See Also
312 --------
313
314 :func:`display`, :func:`update_display`
315
316 """
317
318 def __init__(self, display_id=None):
319 if display_id is None:
320 display_id = _new_id()
321 self.display_id = display_id
322
323 def __repr__(self):
324 return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
325
326 def display(self, obj, **kwargs):
327 """Make a new display with my id, updating existing instances.
328
329 Parameters
330 ----------
331
332 obj:
333 object to display
334 **kwargs:
335 additional keyword arguments passed to display
336 """
337 display(obj, display_id=self.display_id, **kwargs)
338
339 def update(self, obj, **kwargs):
340 """Update existing displays with my id
341
342 Parameters
343 ----------
344
345 obj:
346 object to display
347 **kwargs:
348 additional keyword arguments passed to update_display
349 """
350 update_display(obj, display_id=self.display_id, **kwargs)
351
352
353 def clear_output(wait=False):
354 """Clear the output of the current cell receiving output.
355
356 Parameters
357 ----------
358 wait : bool [default: false]
359 Wait to clear the output until new output is available to replace it."""
360 from IPython.core.interactiveshell import InteractiveShell
361 if InteractiveShell.initialized():
362 InteractiveShell.instance().display_pub.clear_output(wait)
363 else:
364 print('\033[2K\r', end='')
365 sys.stdout.flush()
366 print('\033[2K\r', end='')
367 sys.stderr.flush()
@@ -1,1527 +1,1206 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_hex, 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 sys
14 import warnings
13 import warnings
15 from copy import deepcopy
14 from copy import deepcopy
16 from os.path import splitext
15 from os.path import splitext
17 from pathlib import Path, PurePath
16 from pathlib import Path, PurePath
18
17
19 from IPython.utils.py3compat import cast_unicode
18 from IPython.utils.py3compat import cast_unicode
20 from IPython.testing.skipdoctest import skip_doctest
19 from IPython.testing.skipdoctest import skip_doctest
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 def _merge(d1, d2):
42 """Like update, but merges sub-dicts instead of clobbering at the top level.
43
44 Updates d1 in-place
45 """
46
47 if not isinstance(d2, dict) or not isinstance(d1, dict):
48 return d2
49 for key, value in d2.items():
50 d1[key] = _merge(d1.get(key), value)
51 return d1
52
63
53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
64 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
54 """internal implementation of all display_foo methods
65 """internal implementation of all display_foo methods
55
66
56 Parameters
67 Parameters
57 ----------
68 ----------
58 mimetype : str
69 mimetype : str
59 The mimetype to be published (e.g. 'image/png')
70 The mimetype to be published (e.g. 'image/png')
60 *objs : object
71 *objs : object
61 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
62 display.
73 display.
63 raw : bool
74 raw : bool
64 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
65 formatted before display? [default: False]
76 formatted before display? [default: False]
66 metadata : dict (optional)
77 metadata : dict (optional)
67 Metadata to be associated with the specific mimetype output.
78 Metadata to be associated with the specific mimetype output.
68 """
79 """
69 if metadata:
80 if metadata:
70 metadata = {mimetype: metadata}
81 metadata = {mimetype: metadata}
71 if raw:
82 if raw:
72 # turn list of pngdata into list of { 'image/png': pngdata }
83 # turn list of pngdata into list of { 'image/png': pngdata }
73 objs = [ {mimetype: obj} for obj in objs ]
84 objs = [ {mimetype: obj} for obj in objs ]
74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
85 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
75
86
76 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
77 # Main functions
88 # Main functions
78 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
79
90
80 # use * to indicate transient is keyword-only
81 def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs):
82 """Publish data and metadata to all frontends.
83
84 See the ``display_data`` message in the messaging documentation for
85 more details about this message type.
86
87 Keys of data and metadata can be any mime-type.
88
89 Parameters
90 ----------
91 data : dict
92 A dictionary having keys that are valid MIME types (like
93 'text/plain' or 'image/svg+xml') and values that are the data for
94 that MIME type. The data itself must be a JSON'able data
95 structure. Minimally all data should have the 'text/plain' data,
96 which can be displayed by all frontends. If more than the plain
97 text is given, it is up to the frontend to decide which
98 representation to use.
99 metadata : dict
100 A dictionary for metadata related to the data. This can contain
101 arbitrary key, value pairs that frontends can use to interpret
102 the data. mime-type keys matching those in data can be used
103 to specify metadata about particular representations.
104 source : str, deprecated
105 Unused.
106 transient : dict, keyword-only
107 A dictionary of transient data, such as display_id.
108 """
109 from IPython.core.interactiveshell import InteractiveShell
110
111 display_pub = InteractiveShell.instance().display_pub
112
113 # only pass transient if supplied,
114 # to avoid errors with older ipykernel.
115 # TODO: We could check for ipykernel version and provide a detailed upgrade message.
116 if transient:
117 kwargs['transient'] = transient
118
119 display_pub.publish(
120 data=data,
121 metadata=metadata,
122 **kwargs
123 )
124
125
126 def _new_id():
127 """Generate a new random text id with urandom"""
128 return b2a_hex(os.urandom(16)).decode('ascii')
129
130
131 def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs):
132 """Display a Python object in all frontends.
133
134 By default all representations will be computed and sent to the frontends.
135 Frontends can decide which representation is used and how.
136
137 In terminal IPython this will be similar to using :func:`print`, for use in richer
138 frontends see Jupyter notebook examples with rich display logic.
139
140 Parameters
141 ----------
142 *objs : object
143 The Python objects to display.
144 raw : bool, optional
145 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
146 or Python objects that need to be formatted before display? [default: False]
147 include : list, tuple or set, optional
148 A list of format type strings (MIME types) to include in the
149 format data dict. If this is set *only* the format types included
150 in this list will be computed.
151 exclude : list, tuple or set, optional
152 A list of format type strings (MIME types) to exclude in the format
153 data dict. If this is set all format types will be computed,
154 except for those included in this argument.
155 metadata : dict, optional
156 A dictionary of metadata to associate with the output.
157 mime-type keys in this dictionary will be associated with the individual
158 representation formats, if they exist.
159 transient : dict, optional
160 A dictionary of transient data to associate with the output.
161 Data in this dict should not be persisted to files (e.g. notebooks).
162 display_id : str, bool optional
163 Set an id for the display.
164 This id can be used for updating this display area later via update_display.
165 If given as `True`, generate a new `display_id`
166 kwargs: additional keyword-args, optional
167 Additional keyword-arguments are passed through to the display publisher.
168
169 Returns
170 -------
171
172 handle: DisplayHandle
173 Returns a handle on updatable displays for use with :func:`update_display`,
174 if `display_id` is given. Returns :any:`None` if no `display_id` is given
175 (default).
176
177 Examples
178 --------
179
180 >>> class Json(object):
181 ... def __init__(self, json):
182 ... self.json = json
183 ... def _repr_pretty_(self, pp, cycle):
184 ... import json
185 ... pp.text(json.dumps(self.json, indent=2))
186 ... def __repr__(self):
187 ... return str(self.json)
188 ...
189
190 >>> d = Json({1:2, 3: {4:5}})
191
192 >>> print(d)
193 {1: 2, 3: {4: 5}}
194
195 >>> display(d)
196 {
197 "1": 2,
198 "3": {
199 "4": 5
200 }
201 }
202
203 >>> def int_formatter(integer, pp, cycle):
204 ... pp.text('I'*integer)
205
206 >>> plain = get_ipython().display_formatter.formatters['text/plain']
207 >>> plain.for_type(int, int_formatter)
208 <function _repr_pprint at 0x...>
209 >>> display(7-5)
210 II
211
212 >>> del plain.type_printers[int]
213 >>> display(7-5)
214 2
215
216 See Also
217 --------
218
219 :func:`update_display`
220
221 Notes
222 -----
223
224 In Python, objects can declare their textual representation using the
225 `__repr__` method. IPython expands on this idea and allows objects to declare
226 other, rich representations including:
227
228 - HTML
229 - JSON
230 - PNG
231 - JPEG
232 - SVG
233 - LaTeX
234
235 A single object can declare some or all of these representations; all are
236 handled by IPython's display system.
237
238 The main idea of the first approach is that you have to implement special
239 display methods when you define your class, one for each representation you
240 want to use. Here is a list of the names of the special methods and the
241 values they must return:
242
243 - `_repr_html_`: return raw HTML as a string, or a tuple (see below).
244 - `_repr_json_`: return a JSONable dict, or a tuple (see below).
245 - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below).
246 - `_repr_png_`: return raw PNG data, or a tuple (see below).
247 - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below).
248 - `_repr_latex_`: return LaTeX commands in a string surrounded by "$",
249 or a tuple (see below).
250 - `_repr_mimebundle_`: return a full mimebundle containing the mapping
251 from all mimetypes to data.
252 Use this for any mime-type not listed above.
253
254 The above functions may also return the object's metadata alonside the
255 data. If the metadata is available, the functions will return a tuple
256 containing the data and metadata, in that order. If there is no metadata
257 available, then the functions will return the data only.
258
259 When you are directly writing your own classes, you can adapt them for
260 display in IPython by following the above approach. But in practice, you
261 often need to work with existing classes that you can't easily modify.
262
263 You can refer to the documentation on integrating with the display system in
264 order to register custom formatters for already existing types
265 (:ref:`integrating_rich_display`).
266
267 .. versionadded:: 5.4 display available without import
268 .. versionadded:: 6.1 display available without import
269
270 Since IPython 5.4 and 6.1 :func:`display` is automatically made available to
271 the user without import. If you are using display in a document that might
272 be used in a pure python context or with older version of IPython, use the
273 following import at the top of your file::
274
275 from IPython.display import display
276
277 """
278 from IPython.core.interactiveshell import InteractiveShell
279
280 if not InteractiveShell.initialized():
281 # Directly print objects.
282 print(*objs)
283 return
284
285 raw = kwargs.pop('raw', False)
286 if transient is None:
287 transient = {}
288 if metadata is None:
289 metadata={}
290 if display_id:
291 if display_id is True:
292 display_id = _new_id()
293 transient['display_id'] = display_id
294 if kwargs.get('update') and 'display_id' not in transient:
295 raise TypeError('display_id required for update_display')
296 if transient:
297 kwargs['transient'] = transient
298
299 if not objs and display_id:
300 # if given no objects, but still a request for a display_id,
301 # we assume the user wants to insert an empty output that
302 # can be updated later
303 objs = [{}]
304 raw = True
305
306 if not raw:
307 format = InteractiveShell.instance().display_formatter.format
308
309 for obj in objs:
310 if raw:
311 publish_display_data(data=obj, metadata=metadata, **kwargs)
312 else:
313 format_dict, md_dict = format(obj, include=include, exclude=exclude)
314 if not format_dict:
315 # nothing to display (e.g. _ipython_display_ took over)
316 continue
317 if metadata:
318 # kwarg-specified metadata gets precedence
319 _merge(md_dict, metadata)
320 publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
321 if display_id:
322 return DisplayHandle(display_id)
323
324
325 # use * for keyword-only display_id arg
326 def update_display(obj, *, display_id, **kwargs):
327 """Update an existing display by id
328
329 Parameters
330 ----------
331
332 obj:
333 The object with which to update the display
334 display_id: keyword-only
335 The id of the display to update
336
337 See Also
338 --------
339
340 :func:`display`
341 """
342 kwargs['update'] = True
343 display(obj, display_id=display_id, **kwargs)
344
345
346 class DisplayHandle(object):
347 """A handle on an updatable display
348
349 Call `.update(obj)` to display a new object.
350
351 Call `.display(obj`) to add a new instance of this display,
352 and update existing instances.
353
354 See Also
355 --------
356
357 :func:`display`, :func:`update_display`
358
359 """
360
361 def __init__(self, display_id=None):
362 if display_id is None:
363 display_id = _new_id()
364 self.display_id = display_id
365
366 def __repr__(self):
367 return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
368
369 def display(self, obj, **kwargs):
370 """Make a new display with my id, updating existing instances.
371
372 Parameters
373 ----------
374
375 obj:
376 object to display
377 **kwargs:
378 additional keyword arguments passed to display
379 """
380 display(obj, display_id=self.display_id, **kwargs)
381
382 def update(self, obj, **kwargs):
383 """Update existing displays with my id
384
385 Parameters
386 ----------
387
388 obj:
389 object to display
390 **kwargs:
391 additional keyword arguments passed to update_display
392 """
393 update_display(obj, display_id=self.display_id, **kwargs)
394
395
91
396 def display_pretty(*objs, **kwargs):
92 def display_pretty(*objs, **kwargs):
397 """Display the pretty (default) representation of an object.
93 """Display the pretty (default) representation of an object.
398
94
399 Parameters
95 Parameters
400 ----------
96 ----------
401 *objs : object
97 *objs : object
402 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
403 display.
99 display.
404 raw : bool
100 raw : bool
405 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
406 formatted before display? [default: False]
102 formatted before display? [default: False]
407 metadata : dict (optional)
103 metadata : dict (optional)
408 Metadata to be associated with the specific mimetype output.
104 Metadata to be associated with the specific mimetype output.
409 """
105 """
410 _display_mimetype('text/plain', objs, **kwargs)
106 _display_mimetype('text/plain', objs, **kwargs)
411
107
412
108
413 def display_html(*objs, **kwargs):
109 def display_html(*objs, **kwargs):
414 """Display the HTML representation of an object.
110 """Display the HTML representation of an object.
415
111
416 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
417 representation, no HTML will be shown.
113 representation, no HTML will be shown.
418
114
419 Parameters
115 Parameters
420 ----------
116 ----------
421 *objs : object
117 *objs : object
422 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
423 display.
119 display.
424 raw : bool
120 raw : bool
425 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
426 formatted before display? [default: False]
122 formatted before display? [default: False]
427 metadata : dict (optional)
123 metadata : dict (optional)
428 Metadata to be associated with the specific mimetype output.
124 Metadata to be associated with the specific mimetype output.
429 """
125 """
430 _display_mimetype('text/html', objs, **kwargs)
126 _display_mimetype('text/html', objs, **kwargs)
431
127
432
128
433 def display_markdown(*objs, **kwargs):
129 def display_markdown(*objs, **kwargs):
434 """Displays the Markdown representation of an object.
130 """Displays the Markdown representation of an object.
435
131
436 Parameters
132 Parameters
437 ----------
133 ----------
438 *objs : object
134 *objs : object
439 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
440 display.
136 display.
441 raw : bool
137 raw : bool
442 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
443 formatted before display? [default: False]
139 formatted before display? [default: False]
444 metadata : dict (optional)
140 metadata : dict (optional)
445 Metadata to be associated with the specific mimetype output.
141 Metadata to be associated with the specific mimetype output.
446 """
142 """
447
143
448 _display_mimetype('text/markdown', objs, **kwargs)
144 _display_mimetype('text/markdown', objs, **kwargs)
449
145
450
146
451 def display_svg(*objs, **kwargs):
147 def display_svg(*objs, **kwargs):
452 """Display the SVG representation of an object.
148 """Display the SVG representation of an object.
453
149
454 Parameters
150 Parameters
455 ----------
151 ----------
456 *objs : object
152 *objs : object
457 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
458 display.
154 display.
459 raw : bool
155 raw : bool
460 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
461 formatted before display? [default: False]
157 formatted before display? [default: False]
462 metadata : dict (optional)
158 metadata : dict (optional)
463 Metadata to be associated with the specific mimetype output.
159 Metadata to be associated with the specific mimetype output.
464 """
160 """
465 _display_mimetype('image/svg+xml', objs, **kwargs)
161 _display_mimetype('image/svg+xml', objs, **kwargs)
466
162
467
163
468 def display_png(*objs, **kwargs):
164 def display_png(*objs, **kwargs):
469 """Display the PNG representation of an object.
165 """Display the PNG representation of an object.
470
166
471 Parameters
167 Parameters
472 ----------
168 ----------
473 *objs : object
169 *objs : object
474 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
475 display.
171 display.
476 raw : bool
172 raw : bool
477 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
478 formatted before display? [default: False]
174 formatted before display? [default: False]
479 metadata : dict (optional)
175 metadata : dict (optional)
480 Metadata to be associated with the specific mimetype output.
176 Metadata to be associated with the specific mimetype output.
481 """
177 """
482 _display_mimetype('image/png', objs, **kwargs)
178 _display_mimetype('image/png', objs, **kwargs)
483
179
484
180
485 def display_jpeg(*objs, **kwargs):
181 def display_jpeg(*objs, **kwargs):
486 """Display the JPEG representation of an object.
182 """Display the JPEG representation of an object.
487
183
488 Parameters
184 Parameters
489 ----------
185 ----------
490 *objs : object
186 *objs : object
491 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
492 display.
188 display.
493 raw : bool
189 raw : bool
494 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
495 formatted before display? [default: False]
191 formatted before display? [default: False]
496 metadata : dict (optional)
192 metadata : dict (optional)
497 Metadata to be associated with the specific mimetype output.
193 Metadata to be associated with the specific mimetype output.
498 """
194 """
499 _display_mimetype('image/jpeg', objs, **kwargs)
195 _display_mimetype('image/jpeg', objs, **kwargs)
500
196
501
197
502 def display_latex(*objs, **kwargs):
198 def display_latex(*objs, **kwargs):
503 """Display the LaTeX representation of an object.
199 """Display the LaTeX representation of an object.
504
200
505 Parameters
201 Parameters
506 ----------
202 ----------
507 *objs : object
203 *objs : object
508 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
509 display.
205 display.
510 raw : bool
206 raw : bool
511 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
512 formatted before display? [default: False]
208 formatted before display? [default: False]
513 metadata : dict (optional)
209 metadata : dict (optional)
514 Metadata to be associated with the specific mimetype output.
210 Metadata to be associated with the specific mimetype output.
515 """
211 """
516 _display_mimetype('text/latex', objs, **kwargs)
212 _display_mimetype('text/latex', objs, **kwargs)
517
213
518
214
519 def display_json(*objs, **kwargs):
215 def display_json(*objs, **kwargs):
520 """Display the JSON representation of an object.
216 """Display the JSON representation of an object.
521
217
522 Note that not many frontends support displaying JSON.
218 Note that not many frontends support displaying JSON.
523
219
524 Parameters
220 Parameters
525 ----------
221 ----------
526 *objs : object
222 *objs : object
527 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
528 display.
224 display.
529 raw : bool
225 raw : bool
530 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
531 formatted before display? [default: False]
227 formatted before display? [default: False]
532 metadata : dict (optional)
228 metadata : dict (optional)
533 Metadata to be associated with the specific mimetype output.
229 Metadata to be associated with the specific mimetype output.
534 """
230 """
535 _display_mimetype('application/json', objs, **kwargs)
231 _display_mimetype('application/json', objs, **kwargs)
536
232
537
233
538 def display_javascript(*objs, **kwargs):
234 def display_javascript(*objs, **kwargs):
539 """Display the Javascript representation of an object.
235 """Display the Javascript representation of an object.
540
236
541 Parameters
237 Parameters
542 ----------
238 ----------
543 *objs : object
239 *objs : object
544 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
545 display.
241 display.
546 raw : bool
242 raw : bool
547 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
548 formatted before display? [default: False]
244 formatted before display? [default: False]
549 metadata : dict (optional)
245 metadata : dict (optional)
550 Metadata to be associated with the specific mimetype output.
246 Metadata to be associated with the specific mimetype output.
551 """
247 """
552 _display_mimetype('application/javascript', objs, **kwargs)
248 _display_mimetype('application/javascript', objs, **kwargs)
553
249
554
250
555 def display_pdf(*objs, **kwargs):
251 def display_pdf(*objs, **kwargs):
556 """Display the PDF representation of an object.
252 """Display the PDF representation of an object.
557
253
558 Parameters
254 Parameters
559 ----------
255 ----------
560 *objs : object
256 *objs : object
561 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
562 display.
258 display.
563 raw : bool
259 raw : bool
564 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
565 formatted before display? [default: False]
261 formatted before display? [default: False]
566 metadata : dict (optional)
262 metadata : dict (optional)
567 Metadata to be associated with the specific mimetype output.
263 Metadata to be associated with the specific mimetype output.
568 """
264 """
569 _display_mimetype('application/pdf', objs, **kwargs)
265 _display_mimetype('application/pdf', objs, **kwargs)
570
266
571
267
572 #-----------------------------------------------------------------------------
268 #-----------------------------------------------------------------------------
573 # Smart classes
269 # Smart classes
574 #-----------------------------------------------------------------------------
270 #-----------------------------------------------------------------------------
575
271
576
272
577 class DisplayObject(object):
273 class DisplayObject(object):
578 """An object that wraps data to be displayed."""
274 """An object that wraps data to be displayed."""
579
275
580 _read_flags = 'r'
276 _read_flags = 'r'
581 _show_mem_addr = False
277 _show_mem_addr = False
582 metadata = None
278 metadata = None
583
279
584 def __init__(self, data=None, url=None, filename=None, metadata=None):
280 def __init__(self, data=None, url=None, filename=None, metadata=None):
585 """Create a display object given raw data.
281 """Create a display object given raw data.
586
282
587 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
588 display function, it will result in the data being displayed
284 display function, it will result in the data being displayed
589 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
590 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'
591 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
592 and then displayed. If
288 and then displayed. If
593
289
594 Parameters
290 Parameters
595 ----------
291 ----------
596 data : unicode, str or bytes
292 data : unicode, str or bytes
597 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
598 url : unicode
294 url : unicode
599 A URL to download the data from.
295 A URL to download the data from.
600 filename : unicode
296 filename : unicode
601 Path to a local file to load the data from.
297 Path to a local file to load the data from.
602 metadata : dict
298 metadata : dict
603 Dict of metadata associated to be the object when displayed
299 Dict of metadata associated to be the object when displayed
604 """
300 """
605 if isinstance(data, (Path, PurePath)):
301 if isinstance(data, (Path, PurePath)):
606 data = str(data)
302 data = str(data)
607
303
608 if data is not None and isinstance(data, str):
304 if data is not None and isinstance(data, str):
609 if data.startswith('http') and url is None:
305 if data.startswith('http') and url is None:
610 url = data
306 url = data
611 filename = None
307 filename = None
612 data = None
308 data = None
613 elif _safe_exists(data) and filename is None:
309 elif _safe_exists(data) and filename is None:
614 url = None
310 url = None
615 filename = data
311 filename = data
616 data = None
312 data = None
617
313
618 self.url = url
314 self.url = url
619 self.filename = filename
315 self.filename = filename
620 # because of @data.setter methods in
316 # because of @data.setter methods in
621 # subclasses ensure url and filename are set
317 # subclasses ensure url and filename are set
622 # before assigning to self.data
318 # before assigning to self.data
623 self.data = data
319 self.data = data
624
320
625 if metadata is not None:
321 if metadata is not None:
626 self.metadata = metadata
322 self.metadata = metadata
627 elif self.metadata is None:
323 elif self.metadata is None:
628 self.metadata = {}
324 self.metadata = {}
629
325
630 self.reload()
326 self.reload()
631 self._check_data()
327 self._check_data()
632
328
633 def __repr__(self):
329 def __repr__(self):
634 if not self._show_mem_addr:
330 if not self._show_mem_addr:
635 cls = self.__class__
331 cls = self.__class__
636 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
332 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
637 else:
333 else:
638 r = super(DisplayObject, self).__repr__()
334 r = super(DisplayObject, self).__repr__()
639 return r
335 return r
640
336
641 def _check_data(self):
337 def _check_data(self):
642 """Override in subclasses if there's something to check."""
338 """Override in subclasses if there's something to check."""
643 pass
339 pass
644
340
645 def _data_and_metadata(self):
341 def _data_and_metadata(self):
646 """shortcut for returning metadata with shape information, if defined"""
342 """shortcut for returning metadata with shape information, if defined"""
647 if self.metadata:
343 if self.metadata:
648 return self.data, deepcopy(self.metadata)
344 return self.data, deepcopy(self.metadata)
649 else:
345 else:
650 return self.data
346 return self.data
651
347
652 def reload(self):
348 def reload(self):
653 """Reload the raw data from file or URL."""
349 """Reload the raw data from file or URL."""
654 if self.filename is not None:
350 if self.filename is not None:
655 with open(self.filename, self._read_flags) as f:
351 with open(self.filename, self._read_flags) as f:
656 self.data = f.read()
352 self.data = f.read()
657 elif self.url is not None:
353 elif self.url is not None:
658 # Deferred import
354 # Deferred import
659 from urllib.request import urlopen
355 from urllib.request import urlopen
660 response = urlopen(self.url)
356 response = urlopen(self.url)
661 data = response.read()
357 data = response.read()
662 # extract encoding from header, if there is one:
358 # extract encoding from header, if there is one:
663 encoding = None
359 encoding = None
664 if 'content-type' in response.headers:
360 if 'content-type' in response.headers:
665 for sub in response.headers['content-type'].split(';'):
361 for sub in response.headers['content-type'].split(';'):
666 sub = sub.strip()
362 sub = sub.strip()
667 if sub.startswith('charset'):
363 if sub.startswith('charset'):
668 encoding = sub.split('=')[-1].strip()
364 encoding = sub.split('=')[-1].strip()
669 break
365 break
670 if 'content-encoding' in response.headers:
366 if 'content-encoding' in response.headers:
671 # TODO: do deflate?
367 # TODO: do deflate?
672 if 'gzip' in response.headers['content-encoding']:
368 if 'gzip' in response.headers['content-encoding']:
673 import gzip
369 import gzip
674 from io import BytesIO
370 from io import BytesIO
675 with gzip.open(BytesIO(data), 'rt', encoding=encoding) as fp:
371 with gzip.open(BytesIO(data), 'rt', encoding=encoding) as fp:
676 encoding = None
372 encoding = None
677 data = fp.read()
373 data = fp.read()
678
374
679 # decode data, if an encoding was specified
375 # decode data, if an encoding was specified
680 # We only touch self.data once since
376 # We only touch self.data once since
681 # subclasses such as SVG have @data.setter methods
377 # subclasses such as SVG have @data.setter methods
682 # that transform self.data into ... well svg.
378 # that transform self.data into ... well svg.
683 if encoding:
379 if encoding:
684 self.data = data.decode(encoding, 'replace')
380 self.data = data.decode(encoding, 'replace')
685 else:
381 else:
686 self.data = data
382 self.data = data
687
383
688
384
689 class TextDisplayObject(DisplayObject):
385 class TextDisplayObject(DisplayObject):
690 """Validate that display data is text"""
386 """Validate that display data is text"""
691 def _check_data(self):
387 def _check_data(self):
692 if self.data is not None and not isinstance(self.data, str):
388 if self.data is not None and not isinstance(self.data, str):
693 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
389 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
694
390
695 class Pretty(TextDisplayObject):
391 class Pretty(TextDisplayObject):
696
392
697 def _repr_pretty_(self, pp, cycle):
393 def _repr_pretty_(self, pp, cycle):
698 return pp.text(self.data)
394 return pp.text(self.data)
699
395
700
396
701 class HTML(TextDisplayObject):
397 class HTML(TextDisplayObject):
702
398
703 def __init__(self, data=None, url=None, filename=None, metadata=None):
399 def __init__(self, data=None, url=None, filename=None, metadata=None):
704 def warn():
400 def warn():
705 if not data:
401 if not data:
706 return False
402 return False
707
403
708 #
404 #
709 # Avoid calling lower() on the entire data, because it could be a
405 # Avoid calling lower() on the entire data, because it could be a
710 # long string and we're only interested in its beginning and end.
406 # long string and we're only interested in its beginning and end.
711 #
407 #
712 prefix = data[:10].lower()
408 prefix = data[:10].lower()
713 suffix = data[-10:].lower()
409 suffix = data[-10:].lower()
714 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
410 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
715
411
716 if warn():
412 if warn():
717 warnings.warn("Consider using IPython.display.IFrame instead")
413 warnings.warn("Consider using IPython.display.IFrame instead")
718 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
414 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
719
415
720 def _repr_html_(self):
416 def _repr_html_(self):
721 return self._data_and_metadata()
417 return self._data_and_metadata()
722
418
723 def __html__(self):
419 def __html__(self):
724 """
420 """
725 This method exists to inform other HTML-using modules (e.g. Markupsafe,
421 This method exists to inform other HTML-using modules (e.g. Markupsafe,
726 htmltag, etc) that this object is HTML and does not need things like
422 htmltag, etc) that this object is HTML and does not need things like
727 special characters (<>&) escaped.
423 special characters (<>&) escaped.
728 """
424 """
729 return self._repr_html_()
425 return self._repr_html_()
730
426
731
427
732 class Markdown(TextDisplayObject):
428 class Markdown(TextDisplayObject):
733
429
734 def _repr_markdown_(self):
430 def _repr_markdown_(self):
735 return self._data_and_metadata()
431 return self._data_and_metadata()
736
432
737
433
738 class Math(TextDisplayObject):
434 class Math(TextDisplayObject):
739
435
740 def _repr_latex_(self):
436 def _repr_latex_(self):
741 s = r"$\displaystyle %s$" % self.data.strip('$')
437 s = r"$\displaystyle %s$" % self.data.strip('$')
742 if self.metadata:
438 if self.metadata:
743 return s, deepcopy(self.metadata)
439 return s, deepcopy(self.metadata)
744 else:
440 else:
745 return s
441 return s
746
442
747
443
748 class Latex(TextDisplayObject):
444 class Latex(TextDisplayObject):
749
445
750 def _repr_latex_(self):
446 def _repr_latex_(self):
751 return self._data_and_metadata()
447 return self._data_and_metadata()
752
448
753
449
754 class SVG(DisplayObject):
450 class SVG(DisplayObject):
755 """Embed an SVG into the display.
451 """Embed an SVG into the display.
756
452
757 Note if you just want to view a svg image via a URL use `:class:Image` with
453 Note if you just want to view a svg image via a URL use `:class:Image` with
758 a url=URL keyword argument.
454 a url=URL keyword argument.
759 """
455 """
760
456
761 _read_flags = 'rb'
457 _read_flags = 'rb'
762 # wrap data in a property, which extracts the <svg> tag, discarding
458 # wrap data in a property, which extracts the <svg> tag, discarding
763 # document headers
459 # document headers
764 _data = None
460 _data = None
765
461
766 @property
462 @property
767 def data(self):
463 def data(self):
768 return self._data
464 return self._data
769
465
770 @data.setter
466 @data.setter
771 def data(self, svg):
467 def data(self, svg):
772 if svg is None:
468 if svg is None:
773 self._data = None
469 self._data = None
774 return
470 return
775 # parse into dom object
471 # parse into dom object
776 from xml.dom import minidom
472 from xml.dom import minidom
777 x = minidom.parseString(svg)
473 x = minidom.parseString(svg)
778 # get svg tag (should be 1)
474 # get svg tag (should be 1)
779 found_svg = x.getElementsByTagName('svg')
475 found_svg = x.getElementsByTagName('svg')
780 if found_svg:
476 if found_svg:
781 svg = found_svg[0].toxml()
477 svg = found_svg[0].toxml()
782 else:
478 else:
783 # fallback on the input, trust the user
479 # fallback on the input, trust the user
784 # but this is probably an error.
480 # but this is probably an error.
785 pass
481 pass
786 svg = cast_unicode(svg)
482 svg = cast_unicode(svg)
787 self._data = svg
483 self._data = svg
788
484
789 def _repr_svg_(self):
485 def _repr_svg_(self):
790 return self._data_and_metadata()
486 return self._data_and_metadata()
791
487
792 class ProgressBar(DisplayObject):
488 class ProgressBar(DisplayObject):
793 """Progressbar supports displaying a progressbar like element
489 """Progressbar supports displaying a progressbar like element
794 """
490 """
795 def __init__(self, total):
491 def __init__(self, total):
796 """Creates a new progressbar
492 """Creates a new progressbar
797
493
798 Parameters
494 Parameters
799 ----------
495 ----------
800 total : int
496 total : int
801 maximum size of the progressbar
497 maximum size of the progressbar
802 """
498 """
803 self.total = total
499 self.total = total
804 self._progress = 0
500 self._progress = 0
805 self.html_width = '60ex'
501 self.html_width = '60ex'
806 self.text_width = 60
502 self.text_width = 60
807 self._display_id = hexlify(os.urandom(8)).decode('ascii')
503 self._display_id = hexlify(os.urandom(8)).decode('ascii')
808
504
809 def __repr__(self):
505 def __repr__(self):
810 fraction = self.progress / self.total
506 fraction = self.progress / self.total
811 filled = '=' * int(fraction * self.text_width)
507 filled = '=' * int(fraction * self.text_width)
812 rest = ' ' * (self.text_width - len(filled))
508 rest = ' ' * (self.text_width - len(filled))
813 return '[{}{}] {}/{}'.format(
509 return '[{}{}] {}/{}'.format(
814 filled, rest,
510 filled, rest,
815 self.progress, self.total,
511 self.progress, self.total,
816 )
512 )
817
513
818 def _repr_html_(self):
514 def _repr_html_(self):
819 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
515 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
820 self.html_width, self.total, self.progress)
516 self.html_width, self.total, self.progress)
821
517
822 def display(self):
518 def display(self):
823 display(self, display_id=self._display_id)
519 display(self, display_id=self._display_id)
824
520
825 def update(self):
521 def update(self):
826 display(self, display_id=self._display_id, update=True)
522 display(self, display_id=self._display_id, update=True)
827
523
828 @property
524 @property
829 def progress(self):
525 def progress(self):
830 return self._progress
526 return self._progress
831
527
832 @progress.setter
528 @progress.setter
833 def progress(self, value):
529 def progress(self, value):
834 self._progress = value
530 self._progress = value
835 self.update()
531 self.update()
836
532
837 def __iter__(self):
533 def __iter__(self):
838 self.display()
534 self.display()
839 self._progress = -1 # First iteration is 0
535 self._progress = -1 # First iteration is 0
840 return self
536 return self
841
537
842 def __next__(self):
538 def __next__(self):
843 """Returns current value and increments display by one."""
539 """Returns current value and increments display by one."""
844 self.progress += 1
540 self.progress += 1
845 if self.progress < self.total:
541 if self.progress < self.total:
846 return self.progress
542 return self.progress
847 else:
543 else:
848 raise StopIteration()
544 raise StopIteration()
849
545
850 class JSON(DisplayObject):
546 class JSON(DisplayObject):
851 """JSON expects a JSON-able dict or list
547 """JSON expects a JSON-able dict or list
852
548
853 not an already-serialized JSON string.
549 not an already-serialized JSON string.
854
550
855 Scalar types (None, number, string) are not allowed, only dict or list containers.
551 Scalar types (None, number, string) are not allowed, only dict or list containers.
856 """
552 """
857 # wrap data in a property, which warns about passing already-serialized JSON
553 # wrap data in a property, which warns about passing already-serialized JSON
858 _data = None
554 _data = None
859 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
555 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
860 """Create a JSON display object given raw data.
556 """Create a JSON display object given raw data.
861
557
862 Parameters
558 Parameters
863 ----------
559 ----------
864 data : dict or list
560 data : dict or list
865 JSON data to display. Not an already-serialized JSON string.
561 JSON data to display. Not an already-serialized JSON string.
866 Scalar types (None, number, string) are not allowed, only dict
562 Scalar types (None, number, string) are not allowed, only dict
867 or list containers.
563 or list containers.
868 url : unicode
564 url : unicode
869 A URL to download the data from.
565 A URL to download the data from.
870 filename : unicode
566 filename : unicode
871 Path to a local file to load the data from.
567 Path to a local file to load the data from.
872 expanded : boolean
568 expanded : boolean
873 Metadata to control whether a JSON display component is expanded.
569 Metadata to control whether a JSON display component is expanded.
874 metadata: dict
570 metadata: dict
875 Specify extra metadata to attach to the json display object.
571 Specify extra metadata to attach to the json display object.
876 root : str
572 root : str
877 The name of the root element of the JSON tree
573 The name of the root element of the JSON tree
878 """
574 """
879 self.metadata = {
575 self.metadata = {
880 'expanded': expanded,
576 'expanded': expanded,
881 'root': root,
577 'root': root,
882 }
578 }
883 if metadata:
579 if metadata:
884 self.metadata.update(metadata)
580 self.metadata.update(metadata)
885 if kwargs:
581 if kwargs:
886 self.metadata.update(kwargs)
582 self.metadata.update(kwargs)
887 super(JSON, self).__init__(data=data, url=url, filename=filename)
583 super(JSON, self).__init__(data=data, url=url, filename=filename)
888
584
889 def _check_data(self):
585 def _check_data(self):
890 if self.data is not None and not isinstance(self.data, (dict, list)):
586 if self.data is not None and not isinstance(self.data, (dict, list)):
891 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
587 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
892
588
893 @property
589 @property
894 def data(self):
590 def data(self):
895 return self._data
591 return self._data
896
592
897 @data.setter
593 @data.setter
898 def data(self, data):
594 def data(self, data):
899 if isinstance(data, (Path, PurePath)):
595 if isinstance(data, (Path, PurePath)):
900 data = str(data)
596 data = str(data)
901
597
902 if isinstance(data, str):
598 if isinstance(data, str):
903 if self.filename is None and self.url is None:
599 if self.filename is None and self.url is None:
904 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
600 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
905 data = json.loads(data)
601 data = json.loads(data)
906 self._data = data
602 self._data = data
907
603
908 def _data_and_metadata(self):
604 def _data_and_metadata(self):
909 return self.data, self.metadata
605 return self.data, self.metadata
910
606
911 def _repr_json_(self):
607 def _repr_json_(self):
912 return self._data_and_metadata()
608 return self._data_and_metadata()
913
609
914 _css_t = """var link = document.createElement("link");
610 _css_t = """var link = document.createElement("link");
915 link.ref = "stylesheet";
611 link.ref = "stylesheet";
916 link.type = "text/css";
612 link.type = "text/css";
917 link.href = "%s";
613 link.href = "%s";
918 document.head.appendChild(link);
614 document.head.appendChild(link);
919 """
615 """
920
616
921 _lib_t1 = """new Promise(function(resolve, reject) {
617 _lib_t1 = """new Promise(function(resolve, reject) {
922 var script = document.createElement("script");
618 var script = document.createElement("script");
923 script.onload = resolve;
619 script.onload = resolve;
924 script.onerror = reject;
620 script.onerror = reject;
925 script.src = "%s";
621 script.src = "%s";
926 document.head.appendChild(script);
622 document.head.appendChild(script);
927 }).then(() => {
623 }).then(() => {
928 """
624 """
929
625
930 _lib_t2 = """
626 _lib_t2 = """
931 });"""
627 });"""
932
628
933 class GeoJSON(JSON):
629 class GeoJSON(JSON):
934 """GeoJSON expects JSON-able dict
630 """GeoJSON expects JSON-able dict
935
631
936 not an already-serialized JSON string.
632 not an already-serialized JSON string.
937
633
938 Scalar types (None, number, string) are not allowed, only dict containers.
634 Scalar types (None, number, string) are not allowed, only dict containers.
939 """
635 """
940
636
941 def __init__(self, *args, **kwargs):
637 def __init__(self, *args, **kwargs):
942 """Create a GeoJSON display object given raw data.
638 """Create a GeoJSON display object given raw data.
943
639
944 Parameters
640 Parameters
945 ----------
641 ----------
946 data : dict or list
642 data : dict or list
947 VegaLite data. Not an already-serialized JSON string.
643 VegaLite data. Not an already-serialized JSON string.
948 Scalar types (None, number, string) are not allowed, only dict
644 Scalar types (None, number, string) are not allowed, only dict
949 or list containers.
645 or list containers.
950 url_template : string
646 url_template : string
951 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
647 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
952 layer_options : dict
648 layer_options : dict
953 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
649 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
954 url : unicode
650 url : unicode
955 A URL to download the data from.
651 A URL to download the data from.
956 filename : unicode
652 filename : unicode
957 Path to a local file to load the data from.
653 Path to a local file to load the data from.
958 metadata: dict
654 metadata: dict
959 Specify extra metadata to attach to the json display object.
655 Specify extra metadata to attach to the json display object.
960
656
961 Examples
657 Examples
962 --------
658 --------
963
659
964 The following will display an interactive map of Mars with a point of
660 The following will display an interactive map of Mars with a point of
965 interest on frontend that do support GeoJSON display.
661 interest on frontend that do support GeoJSON display.
966
662
967 >>> from IPython.display import GeoJSON
663 >>> from IPython.display import GeoJSON
968
664
969 >>> GeoJSON(data={
665 >>> GeoJSON(data={
970 ... "type": "Feature",
666 ... "type": "Feature",
971 ... "geometry": {
667 ... "geometry": {
972 ... "type": "Point",
668 ... "type": "Point",
973 ... "coordinates": [-81.327, 296.038]
669 ... "coordinates": [-81.327, 296.038]
974 ... }
670 ... }
975 ... },
671 ... },
976 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
672 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
977 ... layer_options={
673 ... layer_options={
978 ... "basemap_id": "celestia_mars-shaded-16k_global",
674 ... "basemap_id": "celestia_mars-shaded-16k_global",
979 ... "attribution" : "Celestia/praesepe",
675 ... "attribution" : "Celestia/praesepe",
980 ... "minZoom" : 0,
676 ... "minZoom" : 0,
981 ... "maxZoom" : 18,
677 ... "maxZoom" : 18,
982 ... })
678 ... })
983 <IPython.core.display.GeoJSON object>
679 <IPython.core.display.GeoJSON object>
984
680
985 In the terminal IPython, you will only see the text representation of
681 In the terminal IPython, you will only see the text representation of
986 the GeoJSON object.
682 the GeoJSON object.
987
683
988 """
684 """
989
685
990 super(GeoJSON, self).__init__(*args, **kwargs)
686 super(GeoJSON, self).__init__(*args, **kwargs)
991
687
992
688
993 def _ipython_display_(self):
689 def _ipython_display_(self):
994 bundle = {
690 bundle = {
995 'application/geo+json': self.data,
691 'application/geo+json': self.data,
996 'text/plain': '<IPython.display.GeoJSON object>'
692 'text/plain': '<IPython.display.GeoJSON object>'
997 }
693 }
998 metadata = {
694 metadata = {
999 'application/geo+json': self.metadata
695 'application/geo+json': self.metadata
1000 }
696 }
1001 display(bundle, metadata=metadata, raw=True)
697 display(bundle, metadata=metadata, raw=True)
1002
698
1003 class Javascript(TextDisplayObject):
699 class Javascript(TextDisplayObject):
1004
700
1005 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
701 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
1006 """Create a Javascript display object given raw data.
702 """Create a Javascript display object given raw data.
1007
703
1008 When this object is returned by an expression or passed to the
704 When this object is returned by an expression or passed to the
1009 display function, it will result in the data being displayed
705 display function, it will result in the data being displayed
1010 in the frontend. If the data is a URL, the data will first be
706 in the frontend. If the data is a URL, the data will first be
1011 downloaded and then displayed.
707 downloaded and then displayed.
1012
708
1013 In the Notebook, the containing element will be available as `element`,
709 In the Notebook, the containing element will be available as `element`,
1014 and jQuery will be available. Content appended to `element` will be
710 and jQuery will be available. Content appended to `element` will be
1015 visible in the output area.
711 visible in the output area.
1016
712
1017 Parameters
713 Parameters
1018 ----------
714 ----------
1019 data : unicode, str or bytes
715 data : unicode, str or bytes
1020 The Javascript source code or a URL to download it from.
716 The Javascript source code or a URL to download it from.
1021 url : unicode
717 url : unicode
1022 A URL to download the data from.
718 A URL to download the data from.
1023 filename : unicode
719 filename : unicode
1024 Path to a local file to load the data from.
720 Path to a local file to load the data from.
1025 lib : list or str
721 lib : list or str
1026 A sequence of Javascript library URLs to load asynchronously before
722 A sequence of Javascript library URLs to load asynchronously before
1027 running the source code. The full URLs of the libraries should
723 running the source code. The full URLs of the libraries should
1028 be given. A single Javascript library URL can also be given as a
724 be given. A single Javascript library URL can also be given as a
1029 string.
725 string.
1030 css: : list or str
726 css: : list or str
1031 A sequence of css files to load before running the source code.
727 A sequence of css files to load before running the source code.
1032 The full URLs of the css files should be given. A single css URL
728 The full URLs of the css files should be given. A single css URL
1033 can also be given as a string.
729 can also be given as a string.
1034 """
730 """
1035 if isinstance(lib, str):
731 if isinstance(lib, str):
1036 lib = [lib]
732 lib = [lib]
1037 elif lib is None:
733 elif lib is None:
1038 lib = []
734 lib = []
1039 if isinstance(css, str):
735 if isinstance(css, str):
1040 css = [css]
736 css = [css]
1041 elif css is None:
737 elif css is None:
1042 css = []
738 css = []
1043 if not isinstance(lib, (list,tuple)):
739 if not isinstance(lib, (list,tuple)):
1044 raise TypeError('expected sequence, got: %r' % lib)
740 raise TypeError('expected sequence, got: %r' % lib)
1045 if not isinstance(css, (list,tuple)):
741 if not isinstance(css, (list,tuple)):
1046 raise TypeError('expected sequence, got: %r' % css)
742 raise TypeError('expected sequence, got: %r' % css)
1047 self.lib = lib
743 self.lib = lib
1048 self.css = css
744 self.css = css
1049 super(Javascript, self).__init__(data=data, url=url, filename=filename)
745 super(Javascript, self).__init__(data=data, url=url, filename=filename)
1050
746
1051 def _repr_javascript_(self):
747 def _repr_javascript_(self):
1052 r = ''
748 r = ''
1053 for c in self.css:
749 for c in self.css:
1054 r += _css_t % c
750 r += _css_t % c
1055 for l in self.lib:
751 for l in self.lib:
1056 r += _lib_t1 % l
752 r += _lib_t1 % l
1057 r += self.data
753 r += self.data
1058 r += _lib_t2*len(self.lib)
754 r += _lib_t2*len(self.lib)
1059 return r
755 return r
1060
756
1061 # constants for identifying png/jpeg data
757 # constants for identifying png/jpeg data
1062 _PNG = b'\x89PNG\r\n\x1a\n'
758 _PNG = b'\x89PNG\r\n\x1a\n'
1063 _JPEG = b'\xff\xd8'
759 _JPEG = b'\xff\xd8'
1064
760
1065 def _pngxy(data):
761 def _pngxy(data):
1066 """read the (width, height) from a PNG header"""
762 """read the (width, height) from a PNG header"""
1067 ihdr = data.index(b'IHDR')
763 ihdr = data.index(b'IHDR')
1068 # next 8 bytes are width/height
764 # next 8 bytes are width/height
1069 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
765 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
1070
766
1071 def _jpegxy(data):
767 def _jpegxy(data):
1072 """read the (width, height) from a JPEG header"""
768 """read the (width, height) from a JPEG header"""
1073 # adapted from http://www.64lines.com/jpeg-width-height
769 # adapted from http://www.64lines.com/jpeg-width-height
1074
770
1075 idx = 4
771 idx = 4
1076 while True:
772 while True:
1077 block_size = struct.unpack('>H', data[idx:idx+2])[0]
773 block_size = struct.unpack('>H', data[idx:idx+2])[0]
1078 idx = idx + block_size
774 idx = idx + block_size
1079 if data[idx:idx+2] == b'\xFF\xC0':
775 if data[idx:idx+2] == b'\xFF\xC0':
1080 # found Start of Frame
776 # found Start of Frame
1081 iSOF = idx
777 iSOF = idx
1082 break
778 break
1083 else:
779 else:
1084 # read another block
780 # read another block
1085 idx += 2
781 idx += 2
1086
782
1087 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
783 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
1088 return w, h
784 return w, h
1089
785
1090 def _gifxy(data):
786 def _gifxy(data):
1091 """read the (width, height) from a GIF header"""
787 """read the (width, height) from a GIF header"""
1092 return struct.unpack('<HH', data[6:10])
788 return struct.unpack('<HH', data[6:10])
1093
789
1094
790
1095 class Image(DisplayObject):
791 class Image(DisplayObject):
1096
792
1097 _read_flags = 'rb'
793 _read_flags = 'rb'
1098 _FMT_JPEG = u'jpeg'
794 _FMT_JPEG = u'jpeg'
1099 _FMT_PNG = u'png'
795 _FMT_PNG = u'png'
1100 _FMT_GIF = u'gif'
796 _FMT_GIF = u'gif'
1101 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
797 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
1102 _MIMETYPES = {
798 _MIMETYPES = {
1103 _FMT_PNG: 'image/png',
799 _FMT_PNG: 'image/png',
1104 _FMT_JPEG: 'image/jpeg',
800 _FMT_JPEG: 'image/jpeg',
1105 _FMT_GIF: 'image/gif',
801 _FMT_GIF: 'image/gif',
1106 }
802 }
1107
803
1108 def __init__(self, data=None, url=None, filename=None, format=None,
804 def __init__(self, data=None, url=None, filename=None, format=None,
1109 embed=None, width=None, height=None, retina=False,
805 embed=None, width=None, height=None, retina=False,
1110 unconfined=False, metadata=None):
806 unconfined=False, metadata=None):
1111 """Create a PNG/JPEG/GIF image object given raw data.
807 """Create a PNG/JPEG/GIF image object given raw data.
1112
808
1113 When this object is returned by an input cell or passed to the
809 When this object is returned by an input cell or passed to the
1114 display function, it will result in the image being displayed
810 display function, it will result in the image being displayed
1115 in the frontend.
811 in the frontend.
1116
812
1117 Parameters
813 Parameters
1118 ----------
814 ----------
1119 data : unicode, str or bytes
815 data : unicode, str or bytes
1120 The raw image data or a URL or filename to load the data from.
816 The raw image data or a URL or filename to load the data from.
1121 This always results in embedded image data.
817 This always results in embedded image data.
1122 url : unicode
818 url : unicode
1123 A URL to download the data from. If you specify `url=`,
819 A URL to download the data from. If you specify `url=`,
1124 the image data will not be embedded unless you also specify `embed=True`.
820 the image data will not be embedded unless you also specify `embed=True`.
1125 filename : unicode
821 filename : unicode
1126 Path to a local file to load the data from.
822 Path to a local file to load the data from.
1127 Images from a file are always embedded.
823 Images from a file are always embedded.
1128 format : unicode
824 format : unicode
1129 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
825 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
1130 for format will be inferred from the filename extension.
826 for format will be inferred from the filename extension.
1131 embed : bool
827 embed : bool
1132 Should the image data be embedded using a data URI (True) or be
828 Should the image data be embedded using a data URI (True) or be
1133 loaded using an <img> tag. Set this to True if you want the image
829 loaded using an <img> tag. Set this to True if you want the image
1134 to be viewable later with no internet connection in the notebook.
830 to be viewable later with no internet connection in the notebook.
1135
831
1136 Default is `True`, unless the keyword argument `url` is set, then
832 Default is `True`, unless the keyword argument `url` is set, then
1137 default value is `False`.
833 default value is `False`.
1138
834
1139 Note that QtConsole is not able to display images if `embed` is set to `False`
835 Note that QtConsole is not able to display images if `embed` is set to `False`
1140 width : int
836 width : int
1141 Width in pixels to which to constrain the image in html
837 Width in pixels to which to constrain the image in html
1142 height : int
838 height : int
1143 Height in pixels to which to constrain the image in html
839 Height in pixels to which to constrain the image in html
1144 retina : bool
840 retina : bool
1145 Automatically set the width and height to half of the measured
841 Automatically set the width and height to half of the measured
1146 width and height.
842 width and height.
1147 This only works for embedded images because it reads the width/height
843 This only works for embedded images because it reads the width/height
1148 from image data.
844 from image data.
1149 For non-embedded images, you can just set the desired display width
845 For non-embedded images, you can just set the desired display width
1150 and height directly.
846 and height directly.
1151 unconfined: bool
847 unconfined: bool
1152 Set unconfined=True to disable max-width confinement of the image.
848 Set unconfined=True to disable max-width confinement of the image.
1153 metadata: dict
849 metadata: dict
1154 Specify extra metadata to attach to the image.
850 Specify extra metadata to attach to the image.
1155
851
1156 Examples
852 Examples
1157 --------
853 --------
1158 # embedded image data, works in qtconsole and notebook
854 # embedded image data, works in qtconsole and notebook
1159 # when passed positionally, the first arg can be any of raw image data,
855 # when passed positionally, the first arg can be any of raw image data,
1160 # a URL, or a filename from which to load image data.
856 # a URL, or a filename from which to load image data.
1161 # The result is always embedding image data for inline images.
857 # The result is always embedding image data for inline images.
1162 Image('http://www.google.fr/images/srpr/logo3w.png')
858 Image('http://www.google.fr/images/srpr/logo3w.png')
1163 Image('/path/to/image.jpg')
859 Image('/path/to/image.jpg')
1164 Image(b'RAW_PNG_DATA...')
860 Image(b'RAW_PNG_DATA...')
1165
861
1166 # Specifying Image(url=...) does not embed the image data,
862 # Specifying Image(url=...) does not embed the image data,
1167 # it only generates `<img>` tag with a link to the source.
863 # it only generates `<img>` tag with a link to the source.
1168 # This will not work in the qtconsole or offline.
864 # This will not work in the qtconsole or offline.
1169 Image(url='http://www.google.fr/images/srpr/logo3w.png')
865 Image(url='http://www.google.fr/images/srpr/logo3w.png')
1170
866
1171 """
867 """
1172 if isinstance(data, (Path, PurePath)):
868 if isinstance(data, (Path, PurePath)):
1173 data = str(data)
869 data = str(data)
1174
870
1175 if filename is not None:
871 if filename is not None:
1176 ext = self._find_ext(filename)
872 ext = self._find_ext(filename)
1177 elif url is not None:
873 elif url is not None:
1178 ext = self._find_ext(url)
874 ext = self._find_ext(url)
1179 elif data is None:
875 elif data is None:
1180 raise ValueError("No image data found. Expecting filename, url, or data.")
876 raise ValueError("No image data found. Expecting filename, url, or data.")
1181 elif isinstance(data, str) and (
877 elif isinstance(data, str) and (
1182 data.startswith('http') or _safe_exists(data)
878 data.startswith('http') or _safe_exists(data)
1183 ):
879 ):
1184 ext = self._find_ext(data)
880 ext = self._find_ext(data)
1185 else:
881 else:
1186 ext = None
882 ext = None
1187
883
1188 if format is None:
884 if format is None:
1189 if ext is not None:
885 if ext is not None:
1190 if ext == u'jpg' or ext == u'jpeg':
886 if ext == u'jpg' or ext == u'jpeg':
1191 format = self._FMT_JPEG
887 format = self._FMT_JPEG
1192 elif ext == u'png':
888 elif ext == u'png':
1193 format = self._FMT_PNG
889 format = self._FMT_PNG
1194 elif ext == u'gif':
890 elif ext == u'gif':
1195 format = self._FMT_GIF
891 format = self._FMT_GIF
1196 else:
892 else:
1197 format = ext.lower()
893 format = ext.lower()
1198 elif isinstance(data, bytes):
894 elif isinstance(data, bytes):
1199 # infer image type from image data header,
895 # infer image type from image data header,
1200 # only if format has not been specified.
896 # only if format has not been specified.
1201 if data[:2] == _JPEG:
897 if data[:2] == _JPEG:
1202 format = self._FMT_JPEG
898 format = self._FMT_JPEG
1203
899
1204 # failed to detect format, default png
900 # failed to detect format, default png
1205 if format is None:
901 if format is None:
1206 format = self._FMT_PNG
902 format = self._FMT_PNG
1207
903
1208 if format.lower() == 'jpg':
904 if format.lower() == 'jpg':
1209 # jpg->jpeg
905 # jpg->jpeg
1210 format = self._FMT_JPEG
906 format = self._FMT_JPEG
1211
907
1212 self.format = format.lower()
908 self.format = format.lower()
1213 self.embed = embed if embed is not None else (url is None)
909 self.embed = embed if embed is not None else (url is None)
1214
910
1215 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
911 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
1216 raise ValueError("Cannot embed the '%s' image format" % (self.format))
912 raise ValueError("Cannot embed the '%s' image format" % (self.format))
1217 if self.embed:
913 if self.embed:
1218 self._mimetype = self._MIMETYPES.get(self.format)
914 self._mimetype = self._MIMETYPES.get(self.format)
1219
915
1220 self.width = width
916 self.width = width
1221 self.height = height
917 self.height = height
1222 self.retina = retina
918 self.retina = retina
1223 self.unconfined = unconfined
919 self.unconfined = unconfined
1224 super(Image, self).__init__(data=data, url=url, filename=filename,
920 super(Image, self).__init__(data=data, url=url, filename=filename,
1225 metadata=metadata)
921 metadata=metadata)
1226
922
1227 if self.width is None and self.metadata.get('width', {}):
923 if self.width is None and self.metadata.get('width', {}):
1228 self.width = metadata['width']
924 self.width = metadata['width']
1229
925
1230 if self.height is None and self.metadata.get('height', {}):
926 if self.height is None and self.metadata.get('height', {}):
1231 self.height = metadata['height']
927 self.height = metadata['height']
1232
928
1233 if retina:
929 if retina:
1234 self._retina_shape()
930 self._retina_shape()
1235
931
1236
932
1237 def _retina_shape(self):
933 def _retina_shape(self):
1238 """load pixel-doubled width and height from image data"""
934 """load pixel-doubled width and height from image data"""
1239 if not self.embed:
935 if not self.embed:
1240 return
936 return
1241 if self.format == self._FMT_PNG:
937 if self.format == self._FMT_PNG:
1242 w, h = _pngxy(self.data)
938 w, h = _pngxy(self.data)
1243 elif self.format == self._FMT_JPEG:
939 elif self.format == self._FMT_JPEG:
1244 w, h = _jpegxy(self.data)
940 w, h = _jpegxy(self.data)
1245 elif self.format == self._FMT_GIF:
941 elif self.format == self._FMT_GIF:
1246 w, h = _gifxy(self.data)
942 w, h = _gifxy(self.data)
1247 else:
943 else:
1248 # retina only supports png
944 # retina only supports png
1249 return
945 return
1250 self.width = w // 2
946 self.width = w // 2
1251 self.height = h // 2
947 self.height = h // 2
1252
948
1253 def reload(self):
949 def reload(self):
1254 """Reload the raw data from file or URL."""
950 """Reload the raw data from file or URL."""
1255 if self.embed:
951 if self.embed:
1256 super(Image,self).reload()
952 super(Image,self).reload()
1257 if self.retina:
953 if self.retina:
1258 self._retina_shape()
954 self._retina_shape()
1259
955
1260 def _repr_html_(self):
956 def _repr_html_(self):
1261 if not self.embed:
957 if not self.embed:
1262 width = height = klass = ''
958 width = height = klass = ''
1263 if self.width:
959 if self.width:
1264 width = ' width="%d"' % self.width
960 width = ' width="%d"' % self.width
1265 if self.height:
961 if self.height:
1266 height = ' height="%d"' % self.height
962 height = ' height="%d"' % self.height
1267 if self.unconfined:
963 if self.unconfined:
1268 klass = ' class="unconfined"'
964 klass = ' class="unconfined"'
1269 return u'<img src="{url}"{width}{height}{klass}/>'.format(
965 return u'<img src="{url}"{width}{height}{klass}/>'.format(
1270 url=self.url,
966 url=self.url,
1271 width=width,
967 width=width,
1272 height=height,
968 height=height,
1273 klass=klass,
969 klass=klass,
1274 )
970 )
1275
971
1276 def _repr_mimebundle_(self, include=None, exclude=None):
972 def _repr_mimebundle_(self, include=None, exclude=None):
1277 """Return the image as a mimebundle
973 """Return the image as a mimebundle
1278
974
1279 Any new mimetype support should be implemented here.
975 Any new mimetype support should be implemented here.
1280 """
976 """
1281 if self.embed:
977 if self.embed:
1282 mimetype = self._mimetype
978 mimetype = self._mimetype
1283 data, metadata = self._data_and_metadata(always_both=True)
979 data, metadata = self._data_and_metadata(always_both=True)
1284 if metadata:
980 if metadata:
1285 metadata = {mimetype: metadata}
981 metadata = {mimetype: metadata}
1286 return {mimetype: data}, metadata
982 return {mimetype: data}, metadata
1287 else:
983 else:
1288 return {'text/html': self._repr_html_()}
984 return {'text/html': self._repr_html_()}
1289
985
1290 def _data_and_metadata(self, always_both=False):
986 def _data_and_metadata(self, always_both=False):
1291 """shortcut for returning metadata with shape information, if defined"""
987 """shortcut for returning metadata with shape information, if defined"""
1292 try:
988 try:
1293 b64_data = b2a_base64(self.data).decode('ascii')
989 b64_data = b2a_base64(self.data).decode('ascii')
1294 except TypeError:
990 except TypeError:
1295 raise FileNotFoundError(
991 raise FileNotFoundError(
1296 "No such file or directory: '%s'" % (self.data))
992 "No such file or directory: '%s'" % (self.data))
1297 md = {}
993 md = {}
1298 if self.metadata:
994 if self.metadata:
1299 md.update(self.metadata)
995 md.update(self.metadata)
1300 if self.width:
996 if self.width:
1301 md['width'] = self.width
997 md['width'] = self.width
1302 if self.height:
998 if self.height:
1303 md['height'] = self.height
999 md['height'] = self.height
1304 if self.unconfined:
1000 if self.unconfined:
1305 md['unconfined'] = self.unconfined
1001 md['unconfined'] = self.unconfined
1306 if md or always_both:
1002 if md or always_both:
1307 return b64_data, md
1003 return b64_data, md
1308 else:
1004 else:
1309 return b64_data
1005 return b64_data
1310
1006
1311 def _repr_png_(self):
1007 def _repr_png_(self):
1312 if self.embed and self.format == self._FMT_PNG:
1008 if self.embed and self.format == self._FMT_PNG:
1313 return self._data_and_metadata()
1009 return self._data_and_metadata()
1314
1010
1315 def _repr_jpeg_(self):
1011 def _repr_jpeg_(self):
1316 if self.embed and self.format == self._FMT_JPEG:
1012 if self.embed and self.format == self._FMT_JPEG:
1317 return self._data_and_metadata()
1013 return self._data_and_metadata()
1318
1014
1319 def _find_ext(self, s):
1015 def _find_ext(self, s):
1320 base, ext = splitext(s)
1016 base, ext = splitext(s)
1321
1017
1322 if not ext:
1018 if not ext:
1323 return base
1019 return base
1324
1020
1325 # `splitext` includes leading period, so we skip it
1021 # `splitext` includes leading period, so we skip it
1326 return ext[1:].lower()
1022 return ext[1:].lower()
1327
1023
1328
1024
1329 class Video(DisplayObject):
1025 class Video(DisplayObject):
1330
1026
1331 def __init__(self, data=None, url=None, filename=None, embed=False,
1027 def __init__(self, data=None, url=None, filename=None, embed=False,
1332 mimetype=None, width=None, height=None, html_attributes="controls"):
1028 mimetype=None, width=None, height=None, html_attributes="controls"):
1333 """Create a video object given raw data or an URL.
1029 """Create a video object given raw data or an URL.
1334
1030
1335 When this object is returned by an input cell or passed to the
1031 When this object is returned by an input cell or passed to the
1336 display function, it will result in the video being displayed
1032 display function, it will result in the video being displayed
1337 in the frontend.
1033 in the frontend.
1338
1034
1339 Parameters
1035 Parameters
1340 ----------
1036 ----------
1341 data : unicode, str or bytes
1037 data : unicode, str or bytes
1342 The raw video data or a URL or filename to load the data from.
1038 The raw video data or a URL or filename to load the data from.
1343 Raw data will require passing `embed=True`.
1039 Raw data will require passing `embed=True`.
1344 url : unicode
1040 url : unicode
1345 A URL for the video. If you specify `url=`,
1041 A URL for the video. If you specify `url=`,
1346 the image data will not be embedded.
1042 the image data will not be embedded.
1347 filename : unicode
1043 filename : unicode
1348 Path to a local file containing the video.
1044 Path to a local file containing the video.
1349 Will be interpreted as a local URL unless `embed=True`.
1045 Will be interpreted as a local URL unless `embed=True`.
1350 embed : bool
1046 embed : bool
1351 Should the video be embedded using a data URI (True) or be
1047 Should the video be embedded using a data URI (True) or be
1352 loaded using a <video> tag (False).
1048 loaded using a <video> tag (False).
1353
1049
1354 Since videos are large, embedding them should be avoided, if possible.
1050 Since videos are large, embedding them should be avoided, if possible.
1355 You must confirm embedding as your intention by passing `embed=True`.
1051 You must confirm embedding as your intention by passing `embed=True`.
1356
1052
1357 Local files can be displayed with URLs without embedding the content, via::
1053 Local files can be displayed with URLs without embedding the content, via::
1358
1054
1359 Video('./video.mp4')
1055 Video('./video.mp4')
1360
1056
1361 mimetype: unicode
1057 mimetype: unicode
1362 Specify the mimetype for embedded videos.
1058 Specify the mimetype for embedded videos.
1363 Default will be guessed from file extension, if available.
1059 Default will be guessed from file extension, if available.
1364 width : int
1060 width : int
1365 Width in pixels to which to constrain the video in HTML.
1061 Width in pixels to which to constrain the video in HTML.
1366 If not supplied, defaults to the width of the video.
1062 If not supplied, defaults to the width of the video.
1367 height : int
1063 height : int
1368 Height in pixels to which to constrain the video in html.
1064 Height in pixels to which to constrain the video in html.
1369 If not supplied, defaults to the height of the video.
1065 If not supplied, defaults to the height of the video.
1370 html_attributes : str
1066 html_attributes : str
1371 Attributes for the HTML `<video>` block.
1067 Attributes for the HTML `<video>` block.
1372 Default: `"controls"` to get video controls.
1068 Default: `"controls"` to get video controls.
1373 Other examples: `"controls muted"` for muted video with controls,
1069 Other examples: `"controls muted"` for muted video with controls,
1374 `"loop autoplay"` for looping autoplaying video without controls.
1070 `"loop autoplay"` for looping autoplaying video without controls.
1375
1071
1376 Examples
1072 Examples
1377 --------
1073 --------
1378
1074
1379 ::
1075 ::
1380
1076
1381 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1077 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1382 Video('path/to/video.mp4')
1078 Video('path/to/video.mp4')
1383 Video('path/to/video.mp4', embed=True)
1079 Video('path/to/video.mp4', embed=True)
1384 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1080 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1385 Video(b'raw-videodata', embed=True)
1081 Video(b'raw-videodata', embed=True)
1386 """
1082 """
1387 if isinstance(data, (Path, PurePath)):
1083 if isinstance(data, (Path, PurePath)):
1388 data = str(data)
1084 data = str(data)
1389
1085
1390 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1086 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1391 url = data
1087 url = data
1392 data = None
1088 data = None
1393 elif os.path.exists(data):
1089 elif os.path.exists(data):
1394 filename = data
1090 filename = data
1395 data = None
1091 data = None
1396
1092
1397 if data and not embed:
1093 if data and not embed:
1398 msg = ''.join([
1094 msg = ''.join([
1399 "To embed videos, you must pass embed=True ",
1095 "To embed videos, you must pass embed=True ",
1400 "(this may make your notebook files huge)\n",
1096 "(this may make your notebook files huge)\n",
1401 "Consider passing Video(url='...')",
1097 "Consider passing Video(url='...')",
1402 ])
1098 ])
1403 raise ValueError(msg)
1099 raise ValueError(msg)
1404
1100
1405 self.mimetype = mimetype
1101 self.mimetype = mimetype
1406 self.embed = embed
1102 self.embed = embed
1407 self.width = width
1103 self.width = width
1408 self.height = height
1104 self.height = height
1409 self.html_attributes = html_attributes
1105 self.html_attributes = html_attributes
1410 super(Video, self).__init__(data=data, url=url, filename=filename)
1106 super(Video, self).__init__(data=data, url=url, filename=filename)
1411
1107
1412 def _repr_html_(self):
1108 def _repr_html_(self):
1413 width = height = ''
1109 width = height = ''
1414 if self.width:
1110 if self.width:
1415 width = ' width="%d"' % self.width
1111 width = ' width="%d"' % self.width
1416 if self.height:
1112 if self.height:
1417 height = ' height="%d"' % self.height
1113 height = ' height="%d"' % self.height
1418
1114
1419 # External URLs and potentially local files are not embedded into the
1115 # External URLs and potentially local files are not embedded into the
1420 # notebook output.
1116 # notebook output.
1421 if not self.embed:
1117 if not self.embed:
1422 url = self.url if self.url is not None else self.filename
1118 url = self.url if self.url is not None else self.filename
1423 output = """<video src="{0}" {1} {2} {3}>
1119 output = """<video src="{0}" {1} {2} {3}>
1424 Your browser does not support the <code>video</code> element.
1120 Your browser does not support the <code>video</code> element.
1425 </video>""".format(url, self.html_attributes, width, height)
1121 </video>""".format(url, self.html_attributes, width, height)
1426 return output
1122 return output
1427
1123
1428 # Embedded videos are base64-encoded.
1124 # Embedded videos are base64-encoded.
1429 mimetype = self.mimetype
1125 mimetype = self.mimetype
1430 if self.filename is not None:
1126 if self.filename is not None:
1431 if not mimetype:
1127 if not mimetype:
1432 mimetype, _ = mimetypes.guess_type(self.filename)
1128 mimetype, _ = mimetypes.guess_type(self.filename)
1433
1129
1434 with open(self.filename, 'rb') as f:
1130 with open(self.filename, 'rb') as f:
1435 video = f.read()
1131 video = f.read()
1436 else:
1132 else:
1437 video = self.data
1133 video = self.data
1438 if isinstance(video, str):
1134 if isinstance(video, str):
1439 # unicode input is already b64-encoded
1135 # unicode input is already b64-encoded
1440 b64_video = video
1136 b64_video = video
1441 else:
1137 else:
1442 b64_video = b2a_base64(video).decode('ascii').rstrip()
1138 b64_video = b2a_base64(video).decode('ascii').rstrip()
1443
1139
1444 output = """<video {0} {1} {2}>
1140 output = """<video {0} {1} {2}>
1445 <source src="data:{3};base64,{4}" type="{3}">
1141 <source src="data:{3};base64,{4}" type="{3}">
1446 Your browser does not support the video tag.
1142 Your browser does not support the video tag.
1447 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1143 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1448 return output
1144 return output
1449
1145
1450 def reload(self):
1146 def reload(self):
1451 # TODO
1147 # TODO
1452 pass
1148 pass
1453
1149
1454
1150
1455 def clear_output(wait=False):
1456 """Clear the output of the current cell receiving output.
1457
1458 Parameters
1459 ----------
1460 wait : bool [default: false]
1461 Wait to clear the output until new output is available to replace it."""
1462 from IPython.core.interactiveshell import InteractiveShell
1463 if InteractiveShell.initialized():
1464 InteractiveShell.instance().display_pub.clear_output(wait)
1465 else:
1466 print('\033[2K\r', end='')
1467 sys.stdout.flush()
1468 print('\033[2K\r', end='')
1469 sys.stderr.flush()
1470
1471
1472 @skip_doctest
1151 @skip_doctest
1473 def set_matplotlib_formats(*formats, **kwargs):
1152 def set_matplotlib_formats(*formats, **kwargs):
1474 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1153 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1475
1154
1476 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1155 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1477
1156
1478 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1157 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1479
1158
1480 To set this in your config files use the following::
1159 To set this in your config files use the following::
1481
1160
1482 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1161 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1483 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1162 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1484
1163
1485 Parameters
1164 Parameters
1486 ----------
1165 ----------
1487 *formats : strs
1166 *formats : strs
1488 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1167 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1489 **kwargs :
1168 **kwargs :
1490 Keyword args will be relayed to ``figure.canvas.print_figure``.
1169 Keyword args will be relayed to ``figure.canvas.print_figure``.
1491 """
1170 """
1492 from IPython.core.interactiveshell import InteractiveShell
1171 from IPython.core.interactiveshell import InteractiveShell
1493 from IPython.core.pylabtools import select_figure_formats
1172 from IPython.core.pylabtools import select_figure_formats
1494 # build kwargs, starting with InlineBackend config
1173 # build kwargs, starting with InlineBackend config
1495 kw = {}
1174 kw = {}
1496 from ipykernel.pylab.config import InlineBackend
1175 from ipykernel.pylab.config import InlineBackend
1497 cfg = InlineBackend.instance()
1176 cfg = InlineBackend.instance()
1498 kw.update(cfg.print_figure_kwargs)
1177 kw.update(cfg.print_figure_kwargs)
1499 kw.update(**kwargs)
1178 kw.update(**kwargs)
1500 shell = InteractiveShell.instance()
1179 shell = InteractiveShell.instance()
1501 select_figure_formats(shell, formats, **kw)
1180 select_figure_formats(shell, formats, **kw)
1502
1181
1503 @skip_doctest
1182 @skip_doctest
1504 def set_matplotlib_close(close=True):
1183 def set_matplotlib_close(close=True):
1505 """Set whether the inline backend closes all figures automatically or not.
1184 """Set whether the inline backend closes all figures automatically or not.
1506
1185
1507 By default, the inline backend used in the IPython Notebook will close all
1186 By default, the inline backend used in the IPython Notebook will close all
1508 matplotlib figures automatically after each cell is run. This means that
1187 matplotlib figures automatically after each cell is run. This means that
1509 plots in different cells won't interfere. Sometimes, you may want to make
1188 plots in different cells won't interfere. Sometimes, you may want to make
1510 a plot in one cell and then refine it in later cells. This can be accomplished
1189 a plot in one cell and then refine it in later cells. This can be accomplished
1511 by::
1190 by::
1512
1191
1513 In [1]: set_matplotlib_close(False)
1192 In [1]: set_matplotlib_close(False)
1514
1193
1515 To set this in your config files use the following::
1194 To set this in your config files use the following::
1516
1195
1517 c.InlineBackend.close_figures = False
1196 c.InlineBackend.close_figures = False
1518
1197
1519 Parameters
1198 Parameters
1520 ----------
1199 ----------
1521 close : bool
1200 close : bool
1522 Should all matplotlib figures be automatically closed after each cell is
1201 Should all matplotlib figures be automatically closed after each cell is
1523 run?
1202 run?
1524 """
1203 """
1525 from ipykernel.pylab.config import InlineBackend
1204 from ipykernel.pylab.config import InlineBackend
1526 cfg = InlineBackend.instance()
1205 cfg = InlineBackend.instance()
1527 cfg.close_figures = close
1206 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,460 +1,460 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_embed_svg_url():
75 def test_embed_svg_url():
76 import gzip
76 import gzip
77 from io import BytesIO
77 from io import BytesIO
78 svg_data = b'<svg><circle x="0" y="0" r="1"/></svg>'
78 svg_data = b'<svg><circle x="0" y="0" r="1"/></svg>'
79 url = 'http://test.com/circle.svg'
79 url = 'http://test.com/circle.svg'
80
80
81 gzip_svg = BytesIO()
81 gzip_svg = BytesIO()
82 with gzip.open(gzip_svg, 'wb') as fp:
82 with gzip.open(gzip_svg, 'wb') as fp:
83 fp.write(svg_data)
83 fp.write(svg_data)
84 gzip_svg = gzip_svg.getvalue()
84 gzip_svg = gzip_svg.getvalue()
85
85
86 def mocked_urlopen(*args, **kwargs):
86 def mocked_urlopen(*args, **kwargs):
87 class MockResponse:
87 class MockResponse:
88 def __init__(self, svg):
88 def __init__(self, svg):
89 self._svg_data = svg
89 self._svg_data = svg
90 self.headers = {'content-type': 'image/svg+xml'}
90 self.headers = {'content-type': 'image/svg+xml'}
91
91
92 def read(self):
92 def read(self):
93 return self._svg_data
93 return self._svg_data
94
94
95 if args[0] == url:
95 if args[0] == url:
96 return MockResponse(svg_data)
96 return MockResponse(svg_data)
97 elif args[0] == url + 'z':
97 elif args[0] == url + 'z':
98 ret= MockResponse(gzip_svg)
98 ret= MockResponse(gzip_svg)
99 ret.headers['content-encoding']= 'gzip'
99 ret.headers['content-encoding']= 'gzip'
100 return ret
100 return ret
101 return MockResponse(None)
101 return MockResponse(None)
102
102
103 with mock.patch('urllib.request.urlopen', side_effect=mocked_urlopen):
103 with mock.patch('urllib.request.urlopen', side_effect=mocked_urlopen):
104 svg = display.SVG(url=url)
104 svg = display.SVG(url=url)
105 nt.assert_true(svg._repr_svg_().startswith('<svg'))
105 nt.assert_true(svg._repr_svg_().startswith('<svg'))
106 svg = display.SVG(url=url + 'z')
106 svg = display.SVG(url=url + 'z')
107 nt.assert_true(svg._repr_svg_().startswith('<svg'))
107 nt.assert_true(svg._repr_svg_().startswith('<svg'))
108
108
109 # do it for real: 6.1kB of data
109 # do it for real: 6.1kB of data
110 url = "https://upload.wikimedia.org/wikipedia/commons/3/30/Vector-based_example.svg"
110 url = "https://upload.wikimedia.org/wikipedia/commons/3/30/Vector-based_example.svg"
111 svg = display.SVG(url=url)
111 svg = display.SVG(url=url)
112 nt.assert_true(svg._repr_svg_().startswith('<svg'))
112 nt.assert_true(svg._repr_svg_().startswith('<svg'))
113
113
114 def test_retina_jpeg():
114 def test_retina_jpeg():
115 here = os.path.dirname(__file__)
115 here = os.path.dirname(__file__)
116 img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
116 img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
117 nt.assert_equal(img.height, 1)
117 nt.assert_equal(img.height, 1)
118 nt.assert_equal(img.width, 1)
118 nt.assert_equal(img.width, 1)
119 data, md = img._repr_jpeg_()
119 data, md = img._repr_jpeg_()
120 nt.assert_equal(md['width'], 1)
120 nt.assert_equal(md['width'], 1)
121 nt.assert_equal(md['height'], 1)
121 nt.assert_equal(md['height'], 1)
122
122
123 def test_base64image():
123 def test_base64image():
124 display.Image("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC")
124 display.Image("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC")
125
125
126 def test_image_filename_defaults():
126 def test_image_filename_defaults():
127 '''test format constraint, and validity of jpeg and png'''
127 '''test format constraint, and validity of jpeg and png'''
128 tpath = ipath.get_ipython_package_dir()
128 tpath = ipath.get_ipython_package_dir()
129 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.zip'),
129 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.zip'),
130 embed=True)
130 embed=True)
131 nt.assert_raises(ValueError, display.Image)
131 nt.assert_raises(ValueError, display.Image)
132 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
132 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
133 # check boths paths to allow packages to test at build and install time
133 # check boths paths to allow packages to test at build and install time
134 imgfile = os.path.join(tpath, 'core/tests/2x2.png')
134 imgfile = os.path.join(tpath, 'core/tests/2x2.png')
135 img = display.Image(filename=imgfile)
135 img = display.Image(filename=imgfile)
136 nt.assert_equal('png', img.format)
136 nt.assert_equal('png', img.format)
137 nt.assert_is_not_none(img._repr_png_())
137 nt.assert_is_not_none(img._repr_png_())
138 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
138 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
139 nt.assert_equal('jpeg', img.format)
139 nt.assert_equal('jpeg', img.format)
140 nt.assert_is_none(img._repr_jpeg_())
140 nt.assert_is_none(img._repr_jpeg_())
141
141
142 def _get_inline_config():
142 def _get_inline_config():
143 from ipykernel.pylab.config import InlineBackend
143 from ipykernel.pylab.config import InlineBackend
144 return InlineBackend.instance()
144 return InlineBackend.instance()
145
145
146 @dec.skip_without('matplotlib')
146 @dec.skip_without('matplotlib')
147 def test_set_matplotlib_close():
147 def test_set_matplotlib_close():
148 cfg = _get_inline_config()
148 cfg = _get_inline_config()
149 cfg.close_figures = False
149 cfg.close_figures = False
150 display.set_matplotlib_close()
150 display.set_matplotlib_close()
151 assert cfg.close_figures
151 assert cfg.close_figures
152 display.set_matplotlib_close(False)
152 display.set_matplotlib_close(False)
153 assert not cfg.close_figures
153 assert not cfg.close_figures
154
154
155 _fmt_mime_map = {
155 _fmt_mime_map = {
156 'png': 'image/png',
156 'png': 'image/png',
157 'jpeg': 'image/jpeg',
157 'jpeg': 'image/jpeg',
158 'pdf': 'application/pdf',
158 'pdf': 'application/pdf',
159 'retina': 'image/png',
159 'retina': 'image/png',
160 'svg': 'image/svg+xml',
160 'svg': 'image/svg+xml',
161 }
161 }
162
162
163 @dec.skip_without('matplotlib')
163 @dec.skip_without('matplotlib')
164 def test_set_matplotlib_formats():
164 def test_set_matplotlib_formats():
165 from matplotlib.figure import Figure
165 from matplotlib.figure import Figure
166 formatters = get_ipython().display_formatter.formatters
166 formatters = get_ipython().display_formatter.formatters
167 for formats in [
167 for formats in [
168 ('png',),
168 ('png',),
169 ('pdf', 'svg'),
169 ('pdf', 'svg'),
170 ('jpeg', 'retina', 'png'),
170 ('jpeg', 'retina', 'png'),
171 (),
171 (),
172 ]:
172 ]:
173 active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
173 active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
174 display.set_matplotlib_formats(*formats)
174 display.set_matplotlib_formats(*formats)
175 for mime, f in formatters.items():
175 for mime, f in formatters.items():
176 if mime in active_mimes:
176 if mime in active_mimes:
177 nt.assert_in(Figure, f)
177 nt.assert_in(Figure, f)
178 else:
178 else:
179 nt.assert_not_in(Figure, f)
179 nt.assert_not_in(Figure, f)
180
180
181 @dec.skip_without('matplotlib')
181 @dec.skip_without('matplotlib')
182 def test_set_matplotlib_formats_kwargs():
182 def test_set_matplotlib_formats_kwargs():
183 from matplotlib.figure import Figure
183 from matplotlib.figure import Figure
184 ip = get_ipython()
184 ip = get_ipython()
185 cfg = _get_inline_config()
185 cfg = _get_inline_config()
186 cfg.print_figure_kwargs.update(dict(foo='bar'))
186 cfg.print_figure_kwargs.update(dict(foo='bar'))
187 kwargs = dict(quality=10)
187 kwargs = dict(quality=10)
188 display.set_matplotlib_formats('png', **kwargs)
188 display.set_matplotlib_formats('png', **kwargs)
189 formatter = ip.display_formatter.formatters['image/png']
189 formatter = ip.display_formatter.formatters['image/png']
190 f = formatter.lookup_by_type(Figure)
190 f = formatter.lookup_by_type(Figure)
191 cell = f.__closure__[0].cell_contents
191 cell = f.__closure__[0].cell_contents
192 expected = kwargs
192 expected = kwargs
193 expected.update(cfg.print_figure_kwargs)
193 expected.update(cfg.print_figure_kwargs)
194 nt.assert_equal(cell, expected)
194 nt.assert_equal(cell, expected)
195
195
196 def test_display_available():
196 def test_display_available():
197 """
197 """
198 Test that display is available without import
198 Test that display is available without import
199
199
200 We don't really care if it's in builtin or anything else, but it should
200 We don't really care if it's in builtin or anything else, but it should
201 always be available.
201 always be available.
202 """
202 """
203 ip = get_ipython()
203 ip = get_ipython()
204 with AssertNotPrints('NameError'):
204 with AssertNotPrints('NameError'):
205 ip.run_cell('display')
205 ip.run_cell('display')
206 try:
206 try:
207 ip.run_cell('del display')
207 ip.run_cell('del display')
208 except NameError:
208 except NameError:
209 pass # it's ok, it might be in builtins
209 pass # it's ok, it might be in builtins
210 # even if deleted it should be back
210 # even if deleted it should be back
211 with AssertNotPrints('NameError'):
211 with AssertNotPrints('NameError'):
212 ip.run_cell('display')
212 ip.run_cell('display')
213
213
214 def test_textdisplayobj_pretty_repr():
214 def test_textdisplayobj_pretty_repr():
215 p = display.Pretty("This is a simple test")
215 p = display.Pretty("This is a simple test")
216 nt.assert_equal(repr(p), '<IPython.core.display.Pretty object>')
216 nt.assert_equal(repr(p), '<IPython.core.display.Pretty object>')
217 nt.assert_equal(p.data, 'This is a simple test')
217 nt.assert_equal(p.data, 'This is a simple test')
218
218
219 p._show_mem_addr = True
219 p._show_mem_addr = True
220 nt.assert_equal(repr(p), object.__repr__(p))
220 nt.assert_equal(repr(p), object.__repr__(p))
221
221
222 def test_displayobject_repr():
222 def test_displayobject_repr():
223 h = display.HTML('<br />')
223 h = display.HTML('<br />')
224 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
224 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
225 h._show_mem_addr = True
225 h._show_mem_addr = True
226 nt.assert_equal(repr(h), object.__repr__(h))
226 nt.assert_equal(repr(h), object.__repr__(h))
227 h._show_mem_addr = False
227 h._show_mem_addr = False
228 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
228 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
229
229
230 j = display.Javascript('')
230 j = display.Javascript('')
231 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
231 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
232 j._show_mem_addr = True
232 j._show_mem_addr = True
233 nt.assert_equal(repr(j), object.__repr__(j))
233 nt.assert_equal(repr(j), object.__repr__(j))
234 j._show_mem_addr = False
234 j._show_mem_addr = False
235 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
235 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
236
236
237 @mock.patch('warnings.warn')
237 @mock.patch('warnings.warn')
238 def test_encourage_iframe_over_html(m_warn):
238 def test_encourage_iframe_over_html(m_warn):
239 display.HTML()
239 display.HTML()
240 m_warn.assert_not_called()
240 m_warn.assert_not_called()
241
241
242 display.HTML('<br />')
242 display.HTML('<br />')
243 m_warn.assert_not_called()
243 m_warn.assert_not_called()
244
244
245 display.HTML('<html><p>Lots of content here</p><iframe src="http://a.com"></iframe>')
245 display.HTML('<html><p>Lots of content here</p><iframe src="http://a.com"></iframe>')
246 m_warn.assert_not_called()
246 m_warn.assert_not_called()
247
247
248 display.HTML('<iframe src="http://a.com"></iframe>')
248 display.HTML('<iframe src="http://a.com"></iframe>')
249 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
249 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
250
250
251 m_warn.reset_mock()
251 m_warn.reset_mock()
252 display.HTML('<IFRAME SRC="http://a.com"></IFRAME>')
252 display.HTML('<IFRAME SRC="http://a.com"></IFRAME>')
253 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
253 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
254
254
255 def test_progress():
255 def test_progress():
256 p = display.ProgressBar(10)
256 p = display.ProgressBar(10)
257 nt.assert_in('0/10',repr(p))
257 nt.assert_in('0/10',repr(p))
258 p.html_width = '100%'
258 p.html_width = '100%'
259 p.progress = 5
259 p.progress = 5
260 nt.assert_equal(p._repr_html_(), "<progress style='width:100%' max='10' value='5'></progress>")
260 nt.assert_equal(p._repr_html_(), "<progress style='width:100%' max='10' value='5'></progress>")
261
261
262 def test_progress_iter():
262 def test_progress_iter():
263 with capture_output(display=False) as captured:
263 with capture_output(display=False) as captured:
264 for i in display.ProgressBar(5):
264 for i in display.ProgressBar(5):
265 out = captured.stdout
265 out = captured.stdout
266 nt.assert_in('{0}/5'.format(i), out)
266 nt.assert_in('{0}/5'.format(i), out)
267 out = captured.stdout
267 out = captured.stdout
268 nt.assert_in('5/5', out)
268 nt.assert_in('5/5', out)
269
269
270 def test_json():
270 def test_json():
271 d = {'a': 5}
271 d = {'a': 5}
272 lis = [d]
272 lis = [d]
273 metadata = [
273 metadata = [
274 {'expanded': False, 'root': 'root'},
274 {'expanded': False, 'root': 'root'},
275 {'expanded': True, 'root': 'root'},
275 {'expanded': True, 'root': 'root'},
276 {'expanded': False, 'root': 'custom'},
276 {'expanded': False, 'root': 'custom'},
277 {'expanded': True, 'root': 'custom'},
277 {'expanded': True, 'root': 'custom'},
278 ]
278 ]
279 json_objs = [
279 json_objs = [
280 display.JSON(d),
280 display.JSON(d),
281 display.JSON(d, expanded=True),
281 display.JSON(d, expanded=True),
282 display.JSON(d, root='custom'),
282 display.JSON(d, root='custom'),
283 display.JSON(d, expanded=True, root='custom'),
283 display.JSON(d, expanded=True, root='custom'),
284 ]
284 ]
285 for j, md in zip(json_objs, metadata):
285 for j, md in zip(json_objs, metadata):
286 nt.assert_equal(j._repr_json_(), (d, md))
286 nt.assert_equal(j._repr_json_(), (d, md))
287
287
288 with warnings.catch_warnings(record=True) as w:
288 with warnings.catch_warnings(record=True) as w:
289 warnings.simplefilter("always")
289 warnings.simplefilter("always")
290 j = display.JSON(json.dumps(d))
290 j = display.JSON(json.dumps(d))
291 nt.assert_equal(len(w), 1)
291 nt.assert_equal(len(w), 1)
292 nt.assert_equal(j._repr_json_(), (d, metadata[0]))
292 nt.assert_equal(j._repr_json_(), (d, metadata[0]))
293
293
294 json_objs = [
294 json_objs = [
295 display.JSON(lis),
295 display.JSON(lis),
296 display.JSON(lis, expanded=True),
296 display.JSON(lis, expanded=True),
297 display.JSON(lis, root='custom'),
297 display.JSON(lis, root='custom'),
298 display.JSON(lis, expanded=True, root='custom'),
298 display.JSON(lis, expanded=True, root='custom'),
299 ]
299 ]
300 for j, md in zip(json_objs, metadata):
300 for j, md in zip(json_objs, metadata):
301 nt.assert_equal(j._repr_json_(), (lis, md))
301 nt.assert_equal(j._repr_json_(), (lis, md))
302
302
303 with warnings.catch_warnings(record=True) as w:
303 with warnings.catch_warnings(record=True) as w:
304 warnings.simplefilter("always")
304 warnings.simplefilter("always")
305 j = display.JSON(json.dumps(lis))
305 j = display.JSON(json.dumps(lis))
306 nt.assert_equal(len(w), 1)
306 nt.assert_equal(len(w), 1)
307 nt.assert_equal(j._repr_json_(), (lis, metadata[0]))
307 nt.assert_equal(j._repr_json_(), (lis, metadata[0]))
308
308
309 def test_video_embedding():
309 def test_video_embedding():
310 """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
310 """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
311 v = display.Video("http://ignored")
311 v = display.Video("http://ignored")
312 assert not v.embed
312 assert not v.embed
313 html = v._repr_html_()
313 html = v._repr_html_()
314 nt.assert_not_in('src="data:', html)
314 nt.assert_not_in('src="data:', html)
315 nt.assert_in('src="http://ignored"', html)
315 nt.assert_in('src="http://ignored"', html)
316
316
317 with nt.assert_raises(ValueError):
317 with nt.assert_raises(ValueError):
318 v = display.Video(b'abc')
318 v = display.Video(b'abc')
319
319
320 with NamedFileInTemporaryDirectory('test.mp4') as f:
320 with NamedFileInTemporaryDirectory('test.mp4') as f:
321 f.write(b'abc')
321 f.write(b'abc')
322 f.close()
322 f.close()
323
323
324 v = display.Video(f.name)
324 v = display.Video(f.name)
325 assert not v.embed
325 assert not v.embed
326 html = v._repr_html_()
326 html = v._repr_html_()
327 nt.assert_not_in('src="data:', html)
327 nt.assert_not_in('src="data:', html)
328
328
329 v = display.Video(f.name, embed=True)
329 v = display.Video(f.name, embed=True)
330 html = v._repr_html_()
330 html = v._repr_html_()
331 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
331 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
332
332
333 v = display.Video(f.name, embed=True, mimetype='video/other')
333 v = display.Video(f.name, embed=True, mimetype='video/other')
334 html = v._repr_html_()
334 html = v._repr_html_()
335 nt.assert_in('src="data:video/other;base64,YWJj"',html)
335 nt.assert_in('src="data:video/other;base64,YWJj"',html)
336
336
337 v = display.Video(b'abc', embed=True, mimetype='video/mp4')
337 v = display.Video(b'abc', embed=True, mimetype='video/mp4')
338 html = v._repr_html_()
338 html = v._repr_html_()
339 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
339 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
340
340
341 v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
341 v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
342 html = v._repr_html_()
342 html = v._repr_html_()
343 nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
343 nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
344
344
345 def test_html_metadata():
345 def test_html_metadata():
346 s = "<h1>Test</h1>"
346 s = "<h1>Test</h1>"
347 h = display.HTML(s, metadata={"isolated": True})
347 h = display.HTML(s, metadata={"isolated": True})
348 nt.assert_equal(h._repr_html_(), (s, {"isolated": True}))
348 nt.assert_equal(h._repr_html_(), (s, {"isolated": True}))
349
349
350 def test_display_id():
350 def test_display_id():
351 ip = get_ipython()
351 ip = get_ipython()
352 with mock.patch.object(ip.display_pub, 'publish') as pub:
352 with mock.patch.object(ip.display_pub, 'publish') as pub:
353 handle = display.display('x')
353 handle = display.display('x')
354 nt.assert_is(handle, None)
354 nt.assert_is(handle, None)
355 handle = display.display('y', display_id='secret')
355 handle = display.display('y', display_id='secret')
356 nt.assert_is_instance(handle, display.DisplayHandle)
356 nt.assert_is_instance(handle, display.DisplayHandle)
357 handle2 = display.display('z', display_id=True)
357 handle2 = display.display('z', display_id=True)
358 nt.assert_is_instance(handle2, display.DisplayHandle)
358 nt.assert_is_instance(handle2, display.DisplayHandle)
359 nt.assert_not_equal(handle.display_id, handle2.display_id)
359 nt.assert_not_equal(handle.display_id, handle2.display_id)
360
360
361 nt.assert_equal(pub.call_count, 3)
361 nt.assert_equal(pub.call_count, 3)
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 })
369 })
370 args, kwargs = pub.call_args_list[1]
370 args, kwargs = pub.call_args_list[1]
371 nt.assert_equal(args, ())
371 nt.assert_equal(args, ())
372 nt.assert_equal(kwargs, {
372 nt.assert_equal(kwargs, {
373 'data': {
373 'data': {
374 'text/plain': repr('y')
374 'text/plain': repr('y')
375 },
375 },
376 'metadata': {},
376 'metadata': {},
377 'transient': {
377 'transient': {
378 'display_id': handle.display_id,
378 'display_id': handle.display_id,
379 },
379 },
380 })
380 })
381 args, kwargs = pub.call_args_list[2]
381 args, kwargs = pub.call_args_list[2]
382 nt.assert_equal(args, ())
382 nt.assert_equal(args, ())
383 nt.assert_equal(kwargs, {
383 nt.assert_equal(kwargs, {
384 'data': {
384 'data': {
385 'text/plain': repr('z')
385 'text/plain': repr('z')
386 },
386 },
387 'metadata': {},
387 'metadata': {},
388 'transient': {
388 'transient': {
389 'display_id': handle2.display_id,
389 'display_id': handle2.display_id,
390 },
390 },
391 })
391 })
392
392
393
393
394 def test_update_display():
394 def test_update_display():
395 ip = get_ipython()
395 ip = get_ipython()
396 with mock.patch.object(ip.display_pub, 'publish') as pub:
396 with mock.patch.object(ip.display_pub, 'publish') as pub:
397 with nt.assert_raises(TypeError):
397 with nt.assert_raises(TypeError):
398 display.update_display('x')
398 display.update_display('x')
399 display.update_display('x', display_id='1')
399 display.update_display('x', display_id='1')
400 display.update_display('y', display_id='2')
400 display.update_display('y', display_id='2')
401 args, kwargs = pub.call_args_list[0]
401 args, kwargs = pub.call_args_list[0]
402 nt.assert_equal(args, ())
402 nt.assert_equal(args, ())
403 nt.assert_equal(kwargs, {
403 nt.assert_equal(kwargs, {
404 'data': {
404 'data': {
405 'text/plain': repr('x')
405 'text/plain': repr('x')
406 },
406 },
407 'metadata': {},
407 'metadata': {},
408 'transient': {
408 'transient': {
409 'display_id': '1',
409 'display_id': '1',
410 },
410 },
411 'update': True,
411 'update': True,
412 })
412 })
413 args, kwargs = pub.call_args_list[1]
413 args, kwargs = pub.call_args_list[1]
414 nt.assert_equal(args, ())
414 nt.assert_equal(args, ())
415 nt.assert_equal(kwargs, {
415 nt.assert_equal(kwargs, {
416 'data': {
416 'data': {
417 'text/plain': repr('y')
417 'text/plain': repr('y')
418 },
418 },
419 'metadata': {},
419 'metadata': {},
420 'transient': {
420 'transient': {
421 'display_id': '2',
421 'display_id': '2',
422 },
422 },
423 'update': True,
423 'update': True,
424 })
424 })
425
425
426
426
427 def test_display_handle():
427 def test_display_handle():
428 ip = get_ipython()
428 ip = get_ipython()
429 handle = display.DisplayHandle()
429 handle = display.DisplayHandle()
430 nt.assert_is_instance(handle.display_id, str)
430 nt.assert_is_instance(handle.display_id, str)
431 handle = display.DisplayHandle('my-id')
431 handle = display.DisplayHandle('my-id')
432 nt.assert_equal(handle.display_id, 'my-id')
432 nt.assert_equal(handle.display_id, 'my-id')
433 with mock.patch.object(ip.display_pub, 'publish') as pub:
433 with mock.patch.object(ip.display_pub, 'publish') as pub:
434 handle.display('x')
434 handle.display('x')
435 handle.update('y')
435 handle.update('y')
436
436
437 args, kwargs = pub.call_args_list[0]
437 args, kwargs = pub.call_args_list[0]
438 nt.assert_equal(args, ())
438 nt.assert_equal(args, ())
439 nt.assert_equal(kwargs, {
439 nt.assert_equal(kwargs, {
440 'data': {
440 'data': {
441 'text/plain': repr('x')
441 'text/plain': repr('x')
442 },
442 },
443 'metadata': {},
443 'metadata': {},
444 'transient': {
444 'transient': {
445 'display_id': handle.display_id,
445 'display_id': handle.display_id,
446 }
446 }
447 })
447 })
448 args, kwargs = pub.call_args_list[1]
448 args, kwargs = pub.call_args_list[1]
449 nt.assert_equal(args, ())
449 nt.assert_equal(args, ())
450 nt.assert_equal(kwargs, {
450 nt.assert_equal(kwargs, {
451 'data': {
451 'data': {
452 'text/plain': repr('y')
452 'text/plain': repr('y')
453 },
453 },
454 'metadata': {},
454 'metadata': {},
455 'transient': {
455 'transient': {
456 'display_id': handle.display_id,
456 'display_id': handle.display_id,
457 },
457 },
458 'update': True,
458 'update': True,
459 })
459 })
460
460
@@ -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