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