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