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