From 62b25afd4b0f05575e32d0ef685dd559fe43a919 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Thu, 14 Aug 2014 10:04:42 +0200 Subject: [PATCH] Change argument checking in SoundFile.__init__() Check arguments after calling libsndfile instead of before. --- pysoundfile.py | 68 ++++++++++++++------------------------- tests/test_pysoundfile.py | 68 +++++++++++++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 49 deletions(-) diff --git a/pysoundfile.py b/pysoundfile.py index 8452b57..f84cd15 100644 --- a/pysoundfile.py +++ b/pysoundfile.py @@ -334,43 +334,18 @@ def __init__(self, file, mode='r', samplerate=None, channels=None, except KeyError: raise ValueError("Invalid mode: %s" % repr(mode)) - original_format = format - filename = getattr(file, 'name', file) - file_extension = str(filename).rsplit('.', 1)[-1].upper() - if format is None and ('w' in self.mode or - file_extension == 'RAW'): - if file_extension not in _formats: - if self.mode == 'w': - raise TypeError( - "No format specified and unable to get format from " - "file extension: %s" % repr(filename)) - else: - format = file_extension + if format is None: + ext = str(getattr(file, 'name', file)).rsplit('.', 1)[-1].upper() + if ext in _formats: + format = ext self._info = _ffi.new("SF_INFO*") - if self.mode == 'w' or str(format).upper() == 'RAW': - if samplerate is None: - raise TypeError("samplerate must be specified") + if samplerate is not None: self._info.samplerate = samplerate - if channels is None: - raise TypeError("channels must be specified") + if channels is not None: self._info.channels = channels - if str(format).upper() == 'RAW' and subtype is None: - raise TypeError("RAW files must specify a subtype") + if format is not None: self._info.format = _format_int(format, subtype, endian) - elif self.mode == 'rw': - if samplerate is not None: - self._info.samplerate = samplerate - if channels is not None: - self._info.channels = channels - if format is not None: - self._info.format = _format_int(format, subtype, endian) - else: - if [samplerate, channels, original_format, subtype, endian] != \ - [None] * 5: - raise TypeError("Only allowed if mode='w' or format='RAW': " - "samplerate, channels, " - "format, subtype, endian") self._name = file if isinstance(file, str): @@ -383,10 +358,23 @@ def __init__(self, file, mode='r', samplerate=None, channels=None, self._init_virtual_io(file), mode_int, self._info, _ffi.NULL) self._name = str(file) else: - raise RuntimeError("file must be a filename, a file descriptor or " - "a file-like object with the methods " - "'seek()', 'read()', 'write()' and 'tell()'") - self._handle_error() + raise TypeError("Invalid file: %s" % repr(file)) + + err = _snd.sf_error(self._file) + if err != 0 and (self.mode != 'r' or self.format == 'RAW'): + missing = [] + if self._info.samplerate == 0: + missing.append("samplerate") + if self._info.channels == 0: + missing.append("channels") + if self._info.format & _snd.SF_FORMAT_TYPEMASK == 0: + missing.append("format") + if missing: + raise RuntimeError( + "Error opening %r. Missing %s? (libsndfile: %s)" % ( + self.name, "/".join(missing), + _ffi.string(_snd.sf_error_number(err)).decode())) + self._handle_error_number(err) name = property(lambda self: self._name) mode = property(lambda self: self._mode) @@ -502,9 +490,6 @@ def __setattr__(self, name, value): # access text data in the sound file through properties if name in _str_types: self._check_if_closed() - if self.mode == 'r': - raise RuntimeError("Can not change %s of file in read mode" % - repr(name)) data = _ffi.new('char[]', value.encode()) err = _snd.sf_set_string(self._file, _str_types[name], data) self._handle_error_number(err) @@ -557,8 +542,6 @@ def __setitem__(self, frame, data): # access the file as if it where a one-dimensional Numpy # array. Data must be in the form (frames x channels). # Both open slice bounds and negative values are allowed. - if self.mode == 'r': - raise RuntimeError("Cannot write to file opened in read mode") start, stop = self._get_slice_bounds(frame) if stop - start != len(data): raise IndexError( @@ -634,6 +617,7 @@ def _create_empty_array(self, frames, always_2d, dtype): def _read_or_write(self, funcname, array, frames): # Call into libsndfile + self._check_if_closed() ffi_type = _ffi_types[array.dtype] assert array.flags.c_contiguous assert array.dtype.itemsize == _ffi.sizeof(ffi_type) @@ -671,7 +655,6 @@ def read(self, frames=-1, dtype='float64', always_2d=True, containing all valid frames is returned. """ - self._check_if_closed() if self.mode == 'w': raise RuntimeError("Cannot read from file opened in write mode") @@ -709,7 +692,6 @@ def write(self, data): array or as one-dimensional array for mono signals. """ - self._check_if_closed() if self.mode == 'r': raise RuntimeError("Cannot write to file opened in read mode") diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 633738e..e0cebcf 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -312,6 +312,59 @@ def test_blocks_write(sf_stereo_w): list(sf_stereo_w.blocks(blocksize=2)) +# ----------------------------------------------------------------------------- +# Test open() +# ----------------------------------------------------------------------------- + + +def test_open_with_invalid_file(): + with pytest.raises(TypeError) as excinfo: + sf.open(3.1415) + assert "Invalid file" in str(excinfo.value) + + +def test_open_with_invalid_mode(): + with pytest.raises(ValueError) as excinfo: + sf.open(filename_stereo, 42) + assert "Invalid mode" in str(excinfo.value) + with pytest.raises(ValueError) as excinfo: + sf.open(filename_stereo, 'wr') + assert "Invalid mode" in str(excinfo.value) + + +def test_open_with_more_invalid_arguments(): + with pytest.raises(TypeError) as excinfo: + sf.open(filename_new, 'w', samplerate=3.1415, channels=2) + assert "integer" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + sf.open(filename_new, 'w', samplerate=44100, channels=3.1415) + assert "integer" in str(excinfo.value) + with pytest.raises(ValueError) as excinfo: + sf.open(filename_new, 'w', 44100, 2, format='WAF') + assert "Invalid format string" in str(excinfo.value) + with pytest.raises(ValueError) as excinfo: + sf.open(filename_new, 'w', 44100, 2, subtype='PCM16') + assert "Invalid subtype string" in str(excinfo.value) + with pytest.raises(ValueError) as excinfo: + sf.open(filename_new, 'w', 44100, 2, endian='BOTH') + assert "Invalid endian-ness" in str(excinfo.value) + + +def test_open_w_and_rw_new_with_too_few_arguments(tmpdir): + with tmpdir.as_cwd(): + filename = 'not_existing.xyz' + for mode in 'w', 'rw': + with pytest.raises(RuntimeError) as excinfo: + sf.open(filename, mode, samplerate=44100, channels=2) + assert "Missing format" in str(excinfo.value) + with pytest.raises(RuntimeError) as excinfo: + sf.open(filename, mode, samplerate=44100, format='WAV') + assert "channels" in str(excinfo.value) + with pytest.raises(RuntimeError) as excinfo: + sf.open(filename, mode, channels=2, format='WAV') + assert "samplerate" in str(excinfo.value) + + # ----------------------------------------------------------------------------- # Test file metadata # ----------------------------------------------------------------------------- @@ -548,17 +601,20 @@ def test_non_file_attributes_should_not_save_to_disk(): def test_read_raw_files_should_read_data(): - with sf.open(filename_raw, samplerate=44100, - channels=1, subtype='PCM_16') as f: + with sf.open(filename_raw, 'r', 44100, 1, 'PCM_16') as f: assert np.all(f.read(dtype='int16') == data_mono) def test_read_raw_files_with_too_few_arguments_should_fail(): - with pytest.raises(TypeError): # missing everything + with pytest.raises(TypeError) as excinfo: # missing everything sf.open(filename_raw) - with pytest.raises(TypeError): # missing subtype + assert "No default subtype" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: # missing subtype sf.open(filename_raw, samplerate=44100, channels=2) - with pytest.raises(TypeError): # missing channels + assert "No default subtype" in str(excinfo.value) + with pytest.raises(RuntimeError) as excinfo: # missing channels sf.open(filename_raw, samplerate=44100, subtype='PCM_16') - with pytest.raises(TypeError): # missing samplerate + assert "channels" in str(excinfo.value) + with pytest.raises(RuntimeError) as excinfo: # missing samplerate sf.open(filename_raw, channels=2, subtype='PCM_16') + assert "samplerate" in str(excinfo.value)