##// END OF EJS Templates
Fix Audio from data without numpy on Python 3.
Matan Gover -
Show More
@@ -1,621 +1,617 b''
1 1 """Various display related classes.
2 2
3 3 Authors : MinRK, gregcaporaso, dannystaple
4 4 """
5 5 from html import escape as html_escape
6 6 from os.path import exists, isfile, splitext, abspath, join, isdir
7 7 from os import walk, sep, fsdecode
8 8
9 9 from IPython.core.display import DisplayObject, TextDisplayObject
10 10
11 11 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
12 12 'FileLink', 'FileLinks', 'Code']
13 13
14 14
15 15 class Audio(DisplayObject):
16 16 """Create an audio object.
17 17
18 18 When this object is returned by an input cell or passed to the
19 19 display function, it will result in Audio controls being displayed
20 20 in the frontend (only works in the notebook).
21 21
22 22 Parameters
23 23 ----------
24 24 data : numpy array, list, unicode, str or bytes
25 25 Can be one of
26 26
27 27 * Numpy 1d array containing the desired waveform (mono)
28 28 * Numpy 2d array containing waveforms for each channel.
29 29 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
30 30 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
31 31 * List of float or integer representing the waveform (mono)
32 32 * String containing the filename
33 33 * Bytestring containing raw PCM data or
34 34 * URL pointing to a file on the web.
35 35
36 36 If the array option is used, the waveform will be normalized.
37 37
38 38 If a filename or url is used, the format support will be browser
39 39 dependent.
40 40 url : unicode
41 41 A URL to download the data from.
42 42 filename : unicode
43 43 Path to a local file to load the data from.
44 44 embed : boolean
45 45 Should the audio data be embedded using a data URI (True) or should
46 46 the original source be referenced. Set this to True if you want the
47 47 audio to playable later with no internet connection in the notebook.
48 48
49 49 Default is `True`, unless the keyword argument `url` is set, then
50 50 default value is `False`.
51 51 rate : integer
52 52 The sampling rate of the raw data.
53 53 Only required when data parameter is being used as an array
54 54 autoplay : bool
55 55 Set to True if the audio should immediately start playing.
56 56 Default is `False`.
57 57
58 58 Examples
59 59 --------
60 60 ::
61 61
62 62 # Generate a sound
63 63 import numpy as np
64 64 framerate = 44100
65 65 t = np.linspace(0,5,framerate*5)
66 66 data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
67 67 Audio(data,rate=framerate)
68 68
69 69 # Can also do stereo or more channels
70 70 dataleft = np.sin(2*np.pi*220*t)
71 71 dataright = np.sin(2*np.pi*224*t)
72 72 Audio([dataleft, dataright],rate=framerate)
73 73
74 74 Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # From URL
75 75 Audio(url="http://www.w3schools.com/html/horse.ogg")
76 76
77 77 Audio('/path/to/sound.wav') # From file
78 78 Audio(filename='/path/to/sound.ogg')
79 79
80 80 Audio(b'RAW_WAV_DATA..) # From bytes
81 81 Audio(data=b'RAW_WAV_DATA..)
82 82
83 83 """
84 84 _read_flags = 'rb'
85 85
86 86 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False):
87 87 if filename is None and url is None and data is None:
88 88 raise ValueError("No audio data found. Expecting filename, url, or data.")
89 89 if embed is False and url is None:
90 90 raise ValueError("No url found. Expecting url when embed=False")
91 91
92 92 if url is not None and embed is not True:
93 93 self.embed = False
94 94 else:
95 95 self.embed = True
96 96 self.autoplay = autoplay
97 97 super(Audio, self).__init__(data=data, url=url, filename=filename)
98 98
99 99 if self.data is not None and not isinstance(self.data, bytes):
100 100 if rate is None:
101 101 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
102 102 self.data = Audio._make_wav(data, rate)
103 103
104 104 def reload(self):
105 105 """Reload the raw data from file or URL."""
106 106 import mimetypes
107 107 if self.embed:
108 108 super(Audio, self).reload()
109 109
110 110 if self.filename is not None:
111 111 self.mimetype = mimetypes.guess_type(self.filename)[0]
112 112 elif self.url is not None:
113 113 self.mimetype = mimetypes.guess_type(self.url)[0]
114 114 else:
115 115 self.mimetype = "audio/wav"
116 116
117 117 @staticmethod
118 118 def _make_wav(data, rate):
119 119 """ Transform a numpy array to a PCM bytestring """
120 120 import struct
121 121 from io import BytesIO
122 122 import wave
123 123
124 124 try:
125 125 scaled, nchan = Audio._validate_and_normalize_with_numpy(data)
126 126 except ImportError:
127 127 scaled, nchan = Audio._validate_and_normalize_without_numpy(data)
128 128
129 129 fp = BytesIO()
130 130 waveobj = wave.open(fp,mode='wb')
131 131 waveobj.setnchannels(nchan)
132 132 waveobj.setframerate(rate)
133 133 waveobj.setsampwidth(2)
134 134 waveobj.setcomptype('NONE','NONE')
135 135 waveobj.writeframes(b''.join([struct.pack('<h',x) for x in scaled]))
136 136 val = fp.getvalue()
137 137 waveobj.close()
138 138
139 139 return val
140 140
141 141 @staticmethod
142 142 def _validate_and_normalize_with_numpy(data):
143 143 import numpy as np
144 144
145 145 data = np.array(data, dtype=float)
146 146 if len(data.shape) == 1:
147 147 nchan = 1
148 148 elif len(data.shape) == 2:
149 149 # In wave files,channels are interleaved. E.g.,
150 150 # "L1R1L2R2..." for stereo. See
151 151 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
152 152 # for channel ordering
153 153 nchan = data.shape[0]
154 154 data = data.T.ravel()
155 155 else:
156 156 raise ValueError('Array audio input must be a 1D or 2D array')
157 157 scaled = np.int16(data/np.max(np.abs(data))*32767).tolist()
158 158 return scaled, nchan
159 159
160 160 @staticmethod
161 161 def _validate_and_normalize_without_numpy(data):
162 # check that it is a "1D" list
163 idata = iter(data) # fails if not an iterable
164 162 try:
165 iter(idata.next())
163 maxabsvalue = float(max([abs(x) for x in data]))
164 except TypeError:
166 165 raise TypeError('Only lists of mono audio are '
167 166 'supported if numpy is not installed')
168 except TypeError:
169 # this means it's not a nested list, which is what we want
170 pass
171 maxabsvalue = float(max([abs(x) for x in data]))
167
172 168 scaled = [int(x/maxabsvalue*32767) for x in data]
173 169 nchan = 1
174 170 return scaled, nchan
175 171
176 172 def _data_and_metadata(self):
177 173 """shortcut for returning metadata with url information, if defined"""
178 174 md = {}
179 175 if self.url:
180 176 md['url'] = self.url
181 177 if md:
182 178 return self.data, md
183 179 else:
184 180 return self.data
185 181
186 182 def _repr_html_(self):
187 183 src = """
188 184 <audio controls="controls" {autoplay}>
189 185 <source src="{src}" type="{type}" />
190 186 Your browser does not support the audio element.
191 187 </audio>
192 188 """
193 189 return src.format(src=self.src_attr(),type=self.mimetype, autoplay=self.autoplay_attr())
194 190
195 191 def src_attr(self):
196 192 import base64
197 193 if self.embed and (self.data is not None):
198 194 data = base64=base64.b64encode(self.data).decode('ascii')
199 195 return """data:{type};base64,{base64}""".format(type=self.mimetype,
200 196 base64=data)
201 197 elif self.url is not None:
202 198 return self.url
203 199 else:
204 200 return ""
205 201
206 202 def autoplay_attr(self):
207 203 if(self.autoplay):
208 204 return 'autoplay="autoplay"'
209 205 else:
210 206 return ''
211 207
212 208 class IFrame(object):
213 209 """
214 210 Generic class to embed an iframe in an IPython notebook
215 211 """
216 212
217 213 iframe = """
218 214 <iframe
219 215 width="{width}"
220 216 height="{height}"
221 217 src="{src}{params}"
222 218 frameborder="0"
223 219 allowfullscreen
224 220 ></iframe>
225 221 """
226 222
227 223 def __init__(self, src, width, height, **kwargs):
228 224 self.src = src
229 225 self.width = width
230 226 self.height = height
231 227 self.params = kwargs
232 228
233 229 def _repr_html_(self):
234 230 """return the embed iframe"""
235 231 if self.params:
236 232 try:
237 233 from urllib.parse import urlencode # Py 3
238 234 except ImportError:
239 235 from urllib import urlencode
240 236 params = "?" + urlencode(self.params)
241 237 else:
242 238 params = ""
243 239 return self.iframe.format(src=self.src,
244 240 width=self.width,
245 241 height=self.height,
246 242 params=params)
247 243
248 244 class YouTubeVideo(IFrame):
249 245 """Class for embedding a YouTube Video in an IPython session, based on its video id.
250 246
251 247 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
252 248 do::
253 249
254 250 vid = YouTubeVideo("foo")
255 251 display(vid)
256 252
257 253 To start from 30 seconds::
258 254
259 255 vid = YouTubeVideo("abc", start=30)
260 256 display(vid)
261 257
262 258 To calculate seconds from time as hours, minutes, seconds use
263 259 :class:`datetime.timedelta`::
264 260
265 261 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
266 262
267 263 Other parameters can be provided as documented at
268 264 https://developers.google.com/youtube/player_parameters#Parameters
269 265
270 266 When converting the notebook using nbconvert, a jpeg representation of the video
271 267 will be inserted in the document.
272 268 """
273 269
274 270 def __init__(self, id, width=400, height=300, **kwargs):
275 271 self.id=id
276 272 src = "https://www.youtube.com/embed/{0}".format(id)
277 273 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
278 274
279 275 def _repr_jpeg_(self):
280 276 # Deferred import
281 277 from urllib.request import urlopen
282 278
283 279 try:
284 280 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
285 281 except IOError:
286 282 return None
287 283
288 284 class VimeoVideo(IFrame):
289 285 """
290 286 Class for embedding a Vimeo video in an IPython session, based on its video id.
291 287 """
292 288
293 289 def __init__(self, id, width=400, height=300, **kwargs):
294 290 src="https://player.vimeo.com/video/{0}".format(id)
295 291 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
296 292
297 293 class ScribdDocument(IFrame):
298 294 """
299 295 Class for embedding a Scribd document in an IPython session
300 296
301 297 Use the start_page params to specify a starting point in the document
302 298 Use the view_mode params to specify display type one off scroll | slideshow | book
303 299
304 300 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
305 301
306 302 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
307 303 """
308 304
309 305 def __init__(self, id, width=400, height=300, **kwargs):
310 306 src="https://www.scribd.com/embeds/{0}/content".format(id)
311 307 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
312 308
313 309 class FileLink(object):
314 310 """Class for embedding a local file link in an IPython session, based on path
315 311
316 312 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
317 313
318 314 you would do::
319 315
320 316 local_file = FileLink("my/data.txt")
321 317 display(local_file)
322 318
323 319 or in the HTML notebook, just::
324 320
325 321 FileLink("my/data.txt")
326 322 """
327 323
328 324 html_link_str = "<a href='%s' target='_blank'>%s</a>"
329 325
330 326 def __init__(self,
331 327 path,
332 328 url_prefix='',
333 329 result_html_prefix='',
334 330 result_html_suffix='<br>'):
335 331 """
336 332 Parameters
337 333 ----------
338 334 path : str
339 335 path to the file or directory that should be formatted
340 336 url_prefix : str
341 337 prefix to be prepended to all files to form a working link [default:
342 338 '']
343 339 result_html_prefix : str
344 340 text to append to beginning to link [default: '']
345 341 result_html_suffix : str
346 342 text to append at the end of link [default: '<br>']
347 343 """
348 344 if isdir(path):
349 345 raise ValueError("Cannot display a directory using FileLink. "
350 346 "Use FileLinks to display '%s'." % path)
351 347 self.path = fsdecode(path)
352 348 self.url_prefix = url_prefix
353 349 self.result_html_prefix = result_html_prefix
354 350 self.result_html_suffix = result_html_suffix
355 351
356 352 def _format_path(self):
357 353 fp = ''.join([self.url_prefix, html_escape(self.path)])
358 354 return ''.join([self.result_html_prefix,
359 355 self.html_link_str % \
360 356 (fp, html_escape(self.path, quote=False)),
361 357 self.result_html_suffix])
362 358
363 359 def _repr_html_(self):
364 360 """return html link to file
365 361 """
366 362 if not exists(self.path):
367 363 return ("Path (<tt>%s</tt>) doesn't exist. "
368 364 "It may still be in the process of "
369 365 "being generated, or you may have the "
370 366 "incorrect path." % self.path)
371 367
372 368 return self._format_path()
373 369
374 370 def __repr__(self):
375 371 """return absolute path to file
376 372 """
377 373 return abspath(self.path)
378 374
379 375 class FileLinks(FileLink):
380 376 """Class for embedding local file links in an IPython session, based on path
381 377
382 378 e.g. to embed links to files that were generated in the IPython notebook
383 379 under ``my/data``, you would do::
384 380
385 381 local_files = FileLinks("my/data")
386 382 display(local_files)
387 383
388 384 or in the HTML notebook, just::
389 385
390 386 FileLinks("my/data")
391 387 """
392 388 def __init__(self,
393 389 path,
394 390 url_prefix='',
395 391 included_suffixes=None,
396 392 result_html_prefix='',
397 393 result_html_suffix='<br>',
398 394 notebook_display_formatter=None,
399 395 terminal_display_formatter=None,
400 396 recursive=True):
401 397 """
402 398 See :class:`FileLink` for the ``path``, ``url_prefix``,
403 399 ``result_html_prefix`` and ``result_html_suffix`` parameters.
404 400
405 401 included_suffixes : list
406 402 Filename suffixes to include when formatting output [default: include
407 403 all files]
408 404
409 405 notebook_display_formatter : function
410 406 Used to format links for display in the notebook. See discussion of
411 407 formatter functions below.
412 408
413 409 terminal_display_formatter : function
414 410 Used to format links for display in the terminal. See discussion of
415 411 formatter functions below.
416 412
417 413 Formatter functions must be of the form::
418 414
419 415 f(dirname, fnames, included_suffixes)
420 416
421 417 dirname : str
422 418 The name of a directory
423 419 fnames : list
424 420 The files in that directory
425 421 included_suffixes : list
426 422 The file suffixes that should be included in the output (passing None
427 423 meansto include all suffixes in the output in the built-in formatters)
428 424 recursive : boolean
429 425 Whether to recurse into subdirectories. Default is True.
430 426
431 427 The function should return a list of lines that will be printed in the
432 428 notebook (if passing notebook_display_formatter) or the terminal (if
433 429 passing terminal_display_formatter). This function is iterated over for
434 430 each directory in self.path. Default formatters are in place, can be
435 431 passed here to support alternative formatting.
436 432
437 433 """
438 434 if isfile(path):
439 435 raise ValueError("Cannot display a file using FileLinks. "
440 436 "Use FileLink to display '%s'." % path)
441 437 self.included_suffixes = included_suffixes
442 438 # remove trailing slashes for more consistent output formatting
443 439 path = path.rstrip('/')
444 440
445 441 self.path = path
446 442 self.url_prefix = url_prefix
447 443 self.result_html_prefix = result_html_prefix
448 444 self.result_html_suffix = result_html_suffix
449 445
450 446 self.notebook_display_formatter = \
451 447 notebook_display_formatter or self._get_notebook_display_formatter()
452 448 self.terminal_display_formatter = \
453 449 terminal_display_formatter or self._get_terminal_display_formatter()
454 450
455 451 self.recursive = recursive
456 452
457 453 def _get_display_formatter(self,
458 454 dirname_output_format,
459 455 fname_output_format,
460 456 fp_format,
461 457 fp_cleaner=None):
462 458 """ generate built-in formatter function
463 459
464 460 this is used to define both the notebook and terminal built-in
465 461 formatters as they only differ by some wrapper text for each entry
466 462
467 463 dirname_output_format: string to use for formatting directory
468 464 names, dirname will be substituted for a single "%s" which
469 465 must appear in this string
470 466 fname_output_format: string to use for formatting file names,
471 467 if a single "%s" appears in the string, fname will be substituted
472 468 if two "%s" appear in the string, the path to fname will be
473 469 substituted for the first and fname will be substituted for the
474 470 second
475 471 fp_format: string to use for formatting filepaths, must contain
476 472 exactly two "%s" and the dirname will be subsituted for the first
477 473 and fname will be substituted for the second
478 474 """
479 475 def f(dirname, fnames, included_suffixes=None):
480 476 result = []
481 477 # begin by figuring out which filenames, if any,
482 478 # are going to be displayed
483 479 display_fnames = []
484 480 for fname in fnames:
485 481 if (isfile(join(dirname,fname)) and
486 482 (included_suffixes is None or
487 483 splitext(fname)[1] in included_suffixes)):
488 484 display_fnames.append(fname)
489 485
490 486 if len(display_fnames) == 0:
491 487 # if there are no filenames to display, don't print anything
492 488 # (not even the directory name)
493 489 pass
494 490 else:
495 491 # otherwise print the formatted directory name followed by
496 492 # the formatted filenames
497 493 dirname_output_line = dirname_output_format % dirname
498 494 result.append(dirname_output_line)
499 495 for fname in display_fnames:
500 496 fp = fp_format % (dirname,fname)
501 497 if fp_cleaner is not None:
502 498 fp = fp_cleaner(fp)
503 499 try:
504 500 # output can include both a filepath and a filename...
505 501 fname_output_line = fname_output_format % (fp, fname)
506 502 except TypeError:
507 503 # ... or just a single filepath
508 504 fname_output_line = fname_output_format % fname
509 505 result.append(fname_output_line)
510 506 return result
511 507 return f
512 508
513 509 def _get_notebook_display_formatter(self,
514 510 spacer="&nbsp;&nbsp;"):
515 511 """ generate function to use for notebook formatting
516 512 """
517 513 dirname_output_format = \
518 514 self.result_html_prefix + "%s/" + self.result_html_suffix
519 515 fname_output_format = \
520 516 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
521 517 fp_format = self.url_prefix + '%s/%s'
522 518 if sep == "\\":
523 519 # Working on a platform where the path separator is "\", so
524 520 # must convert these to "/" for generating a URI
525 521 def fp_cleaner(fp):
526 522 # Replace all occurrences of backslash ("\") with a forward
527 523 # slash ("/") - this is necessary on windows when a path is
528 524 # provided as input, but we must link to a URI
529 525 return fp.replace('\\','/')
530 526 else:
531 527 fp_cleaner = None
532 528
533 529 return self._get_display_formatter(dirname_output_format,
534 530 fname_output_format,
535 531 fp_format,
536 532 fp_cleaner)
537 533
538 534 def _get_terminal_display_formatter(self,
539 535 spacer=" "):
540 536 """ generate function to use for terminal formatting
541 537 """
542 538 dirname_output_format = "%s/"
543 539 fname_output_format = spacer + "%s"
544 540 fp_format = '%s/%s'
545 541
546 542 return self._get_display_formatter(dirname_output_format,
547 543 fname_output_format,
548 544 fp_format)
549 545
550 546 def _format_path(self):
551 547 result_lines = []
552 548 if self.recursive:
553 549 walked_dir = list(walk(self.path))
554 550 else:
555 551 walked_dir = [next(walk(self.path))]
556 552 walked_dir.sort()
557 553 for dirname, subdirs, fnames in walked_dir:
558 554 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
559 555 return '\n'.join(result_lines)
560 556
561 557 def __repr__(self):
562 558 """return newline-separated absolute paths
563 559 """
564 560 result_lines = []
565 561 if self.recursive:
566 562 walked_dir = list(walk(self.path))
567 563 else:
568 564 walked_dir = [next(walk(self.path))]
569 565 walked_dir.sort()
570 566 for dirname, subdirs, fnames in walked_dir:
571 567 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
572 568 return '\n'.join(result_lines)
573 569
574 570
575 571 class Code(TextDisplayObject):
576 572 """Display syntax-highlighted source code.
577 573
578 574 This uses Pygments to highlight the code for HTML and Latex output.
579 575
580 576 Parameters
581 577 ----------
582 578 data : str
583 579 The code as a string
584 580 url : str
585 581 A URL to fetch the code from
586 582 filename : str
587 583 A local filename to load the code from
588 584 language : str
589 585 The short name of a Pygments lexer to use for highlighting.
590 586 If not specified, it will guess the lexer based on the filename
591 587 or the code. Available lexers: http://pygments.org/docs/lexers/
592 588 """
593 589 def __init__(self, data=None, url=None, filename=None, language=None):
594 590 self.language = language
595 591 super().__init__(data=data, url=url, filename=filename)
596 592
597 593 def _get_lexer(self):
598 594 if self.language:
599 595 from pygments.lexers import get_lexer_by_name
600 596 return get_lexer_by_name(self.language)
601 597 elif self.filename:
602 598 from pygments.lexers import get_lexer_for_filename
603 599 return get_lexer_for_filename(self.filename)
604 600 else:
605 601 from pygments.lexers import guess_lexer
606 602 return guess_lexer(self.data)
607 603
608 604 def __repr__(self):
609 605 return self.data
610 606
611 607 def _repr_html_(self):
612 608 from pygments import highlight
613 609 from pygments.formatters import HtmlFormatter
614 610 fmt = HtmlFormatter()
615 611 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
616 612 return style + highlight(self.data, self._get_lexer(), fmt)
617 613
618 614 def _repr_latex_(self):
619 615 from pygments import highlight
620 616 from pygments.formatters import LatexFormatter
621 617 return highlight(self.data, self._get_lexer(), LatexFormatter())
@@ -1,197 +1,209 b''
1 1 """Tests for IPython.lib.display.
2 2
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2012, the IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from tempfile import NamedTemporaryFile, mkdtemp
16 16 from os.path import split, join as pjoin, dirname
17 17 import sys
18 18 try:
19 19 import pathlib
20 20 except ImportError:
21 21 pass
22 from unittest import mock
22 23
23 24 # Third-party imports
24 25 import nose.tools as nt
25 26 import numpy
26 27
27 28 # Our own imports
28 29 from IPython.lib import display
29 30
30 31 #-----------------------------------------------------------------------------
31 32 # Classes and functions
32 33 #-----------------------------------------------------------------------------
33 34
34 35 #--------------------------
35 36 # FileLink tests
36 37 #--------------------------
37 38
38 39 def test_instantiation_FileLink():
39 40 """FileLink: Test class can be instantiated"""
40 41 fl = display.FileLink('example.txt')
41 42 # TODO: remove if when only Python >= 3.6 is supported
42 43 if sys.version_info >= (3, 6):
43 44 fl = display.FileLink(pathlib.PurePath('example.txt'))
44 45
45 46 def test_warning_on_non_existant_path_FileLink():
46 47 """FileLink: Calling _repr_html_ on non-existant files returns a warning
47 48 """
48 49 fl = display.FileLink('example.txt')
49 50 nt.assert_true(fl._repr_html_().startswith('Path (<tt>example.txt</tt>)'))
50 51
51 52 def test_existing_path_FileLink():
52 53 """FileLink: Calling _repr_html_ functions as expected on existing filepath
53 54 """
54 55 tf = NamedTemporaryFile()
55 56 fl = display.FileLink(tf.name)
56 57 actual = fl._repr_html_()
57 58 expected = "<a href='%s' target='_blank'>%s</a><br>" % (tf.name,tf.name)
58 59 nt.assert_equal(actual,expected)
59 60
60 61 def test_existing_path_FileLink_repr():
61 62 """FileLink: Calling repr() functions as expected on existing filepath
62 63 """
63 64 tf = NamedTemporaryFile()
64 65 fl = display.FileLink(tf.name)
65 66 actual = repr(fl)
66 67 expected = tf.name
67 68 nt.assert_equal(actual,expected)
68 69
69 70 def test_error_on_directory_to_FileLink():
70 71 """FileLink: Raises error when passed directory
71 72 """
72 73 td = mkdtemp()
73 74 nt.assert_raises(ValueError,display.FileLink,td)
74 75
75 76 #--------------------------
76 77 # FileLinks tests
77 78 #--------------------------
78 79
79 80 def test_instantiation_FileLinks():
80 81 """FileLinks: Test class can be instantiated
81 82 """
82 83 fls = display.FileLinks('example')
83 84
84 85 def test_warning_on_non_existant_path_FileLinks():
85 86 """FileLinks: Calling _repr_html_ on non-existant files returns a warning
86 87 """
87 88 fls = display.FileLinks('example')
88 89 nt.assert_true(fls._repr_html_().startswith('Path (<tt>example</tt>)'))
89 90
90 91 def test_existing_path_FileLinks():
91 92 """FileLinks: Calling _repr_html_ functions as expected on existing dir
92 93 """
93 94 td = mkdtemp()
94 95 tf1 = NamedTemporaryFile(dir=td)
95 96 tf2 = NamedTemporaryFile(dir=td)
96 97 fl = display.FileLinks(td)
97 98 actual = fl._repr_html_()
98 99 actual = actual.split('\n')
99 100 actual.sort()
100 101 # the links should always have forward slashes, even on windows, so replace
101 102 # backslashes with forward slashes here
102 103 expected = ["%s/<br>" % td,
103 104 "&nbsp;&nbsp;<a href='%s' target='_blank'>%s</a><br>" %\
104 105 (tf2.name.replace("\\","/"),split(tf2.name)[1]),
105 106 "&nbsp;&nbsp;<a href='%s' target='_blank'>%s</a><br>" %\
106 107 (tf1.name.replace("\\","/"),split(tf1.name)[1])]
107 108 expected.sort()
108 109 # We compare the sorted list of links here as that's more reliable
109 110 nt.assert_equal(actual,expected)
110 111
111 112 def test_existing_path_FileLinks_alt_formatter():
112 113 """FileLinks: Calling _repr_html_ functions as expected w/ an alt formatter
113 114 """
114 115 td = mkdtemp()
115 116 tf1 = NamedTemporaryFile(dir=td)
116 117 tf2 = NamedTemporaryFile(dir=td)
117 118 def fake_formatter(dirname,fnames,included_suffixes):
118 119 return ["hello","world"]
119 120 fl = display.FileLinks(td,notebook_display_formatter=fake_formatter)
120 121 actual = fl._repr_html_()
121 122 actual = actual.split('\n')
122 123 actual.sort()
123 124 expected = ["hello","world"]
124 125 expected.sort()
125 126 # We compare the sorted list of links here as that's more reliable
126 127 nt.assert_equal(actual,expected)
127 128
128 129 def test_existing_path_FileLinks_repr():
129 130 """FileLinks: Calling repr() functions as expected on existing directory """
130 131 td = mkdtemp()
131 132 tf1 = NamedTemporaryFile(dir=td)
132 133 tf2 = NamedTemporaryFile(dir=td)
133 134 fl = display.FileLinks(td)
134 135 actual = repr(fl)
135 136 actual = actual.split('\n')
136 137 actual.sort()
137 138 expected = ['%s/' % td, ' %s' % split(tf1.name)[1],' %s' % split(tf2.name)[1]]
138 139 expected.sort()
139 140 # We compare the sorted list of links here as that's more reliable
140 141 nt.assert_equal(actual,expected)
141 142
142 143 def test_existing_path_FileLinks_repr_alt_formatter():
143 144 """FileLinks: Calling repr() functions as expected w/ alt formatter
144 145 """
145 146 td = mkdtemp()
146 147 tf1 = NamedTemporaryFile(dir=td)
147 148 tf2 = NamedTemporaryFile(dir=td)
148 149 def fake_formatter(dirname,fnames,included_suffixes):
149 150 return ["hello","world"]
150 151 fl = display.FileLinks(td,terminal_display_formatter=fake_formatter)
151 152 actual = repr(fl)
152 153 actual = actual.split('\n')
153 154 actual.sort()
154 155 expected = ["hello","world"]
155 156 expected.sort()
156 157 # We compare the sorted list of links here as that's more reliable
157 158 nt.assert_equal(actual,expected)
158 159
159 160 def test_error_on_file_to_FileLinks():
160 161 """FileLinks: Raises error when passed file
161 162 """
162 163 td = mkdtemp()
163 164 tf1 = NamedTemporaryFile(dir=td)
164 165 nt.assert_raises(ValueError,display.FileLinks,tf1.name)
165 166
166 167 def test_recursive_FileLinks():
167 168 """FileLinks: Does not recurse when recursive=False
168 169 """
169 170 td = mkdtemp()
170 171 tf = NamedTemporaryFile(dir=td)
171 172 subtd = mkdtemp(dir=td)
172 173 subtf = NamedTemporaryFile(dir=subtd)
173 174 fl = display.FileLinks(td)
174 175 actual = str(fl)
175 176 actual = actual.split('\n')
176 177 nt.assert_equal(len(actual), 4, actual)
177 178 fl = display.FileLinks(td, recursive=False)
178 179 actual = str(fl)
179 180 actual = actual.split('\n')
180 181 nt.assert_equal(len(actual), 2, actual)
181 182
182 183 def test_audio_from_file():
183 184 path = pjoin(dirname(__file__), 'test.wav')
184 185 display.Audio(filename=path)
185 186
186 187 def test_audio_from_numpy_array():
187 188 display.Audio(get_test_tone(), rate=44100)
188 189
190 def test_audio_from_list_without_numpy():
191 # Simulate numpy not installed.
192 with mock.patch('numpy.array', side_effect=ImportError):
193 display.Audio(list(get_test_tone()), rate=44100)
194
195 def test_audio_from_list_without_numpy_raises_for_nested_list():
196 # Simulate numpy not installed.
197 with mock.patch('numpy.array', side_effect=ImportError):
198 stereo_signal = [list(get_test_tone())] * 2
199 nt.assert_raises(TypeError, lambda: display.Audio(stereo_signal, rate=44100))
200
189 201 def test_audio_from_numpy_array_without_rate_raises():
190 202 nt.assert_raises(ValueError, display.Audio, get_test_tone())
191 203
192 204 def get_test_tone():
193 205 return numpy.sin(2 * numpy.pi * 440 * numpy.linspace(0, 1, 44100))
194 206
195 207 def test_code_from_file():
196 208 c = display.Code(filename=__file__)
197 209 assert c._repr_html_().startswith('<style>')
General Comments 0
You need to be logged in to leave comments. Login now