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