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