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