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