Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-31412: wave.open takes a path-like object #3484

Closed
wants to merge 10 commits into from
8 changes: 6 additions & 2 deletions Doc/library/aifc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ Module :mod:`aifc` defines the following function:
.. function:: open(file, mode=None)

Open an AIFF or AIFF-C file and return an object instance with methods that are
described below. The argument *file* is either a string naming a file or a
:term:`file object`. *mode* must be ``'r'`` or ``'rb'`` when the file must be
described below. The argument *file* is either a string or :term:`path-like object`
naming a file or a :term:`file object`.
*mode* must be ``'r'`` or ``'rb'`` when the file must be
opened for reading, or ``'w'`` or ``'wb'`` when the file must be opened for writing.
If omitted, ``file.mode`` is used if it exists, otherwise ``'rb'`` is used. When
used for writing, the file object should be seekable, unless you know ahead of
Expand All @@ -50,6 +51,9 @@ Module :mod:`aifc` defines the following function:
.. versionchanged:: 3.4
Support for the :keyword:`with` statement was added.

.. versionchanged:: 3.11
Added support for :term:`path-like objects <path-like object>`.

Objects returned by :func:`.open` when a file is opened for reading have the
following methods:

Expand Down
7 changes: 5 additions & 2 deletions Doc/library/sunau.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ The :mod:`sunau` module defines the following functions:

.. function:: open(file, mode)

If *file* is a string, open the file by that name, otherwise treat it as a
seekable file-like object. *mode* can be any of
If *file* is a string or :term:`path-like object`, open the file
by that name, otherwise treat it as a seekable :term:`file object`.
*mode* can be any of:

``'r'``
Read only mode.
Expand All @@ -58,6 +59,8 @@ The :mod:`sunau` module defines the following functions:
A *mode* of ``'r'`` returns an :class:`AU_read` object, while a *mode* of ``'w'``
or ``'wb'`` returns an :class:`AU_write` object.

.. versionchanged:: 3.11
Added support for :term:`path-like objects <path-like object>`.

.. function:: openfp(file, mode)

Expand Down
7 changes: 5 additions & 2 deletions Doc/library/wave.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ The :mod:`wave` module defines the following function and exception:

.. function:: open(file, mode=None)

If *file* is a string, open the file by that name, otherwise treat it as a
file-like object. *mode* can be:
If *file* is a string or :term:`path-like object`, open the file
by that name, otherwise treat it as a :term:`file object`. *mode* can be:

``'rb'``
Read only mode.
Expand All @@ -47,6 +47,9 @@ The :mod:`wave` module defines the following function and exception:
.. versionchanged:: 3.4
Added support for unseekable files.

.. versionchanged:: 3.11
Added support for :term:`path-like objects <path-like object>`.

.. function:: openfp(file, mode)

A synonym for :func:`.open`, maintained for backwards compatibility.
Expand Down
8 changes: 8 additions & 0 deletions Lib/aifc.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@

import struct
import builtins
import os
import warnings

__all__ = ["Error", "open", "openfp"]
Expand Down Expand Up @@ -908,6 +909,13 @@ def open(f, mode=None):
mode = f.mode
else:
mode = 'rb'
try:
f = os.fspath(f)
except TypeError:
if not hasattr(f, 'read') and not hasattr(f, 'write'):
raise TypeError('open() takes str, a path-like object, '
+ 'or an open file-like object, '
+ f'not {type(f).__name__!r}') from None
if mode in ('r', 'rb'):
return Aifc_read(f)
elif mode in ('w', 'wb'):
Expand Down
9 changes: 9 additions & 0 deletions Lib/sunau.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"""

from collections import namedtuple
import os
import warnings

_sunau_params = namedtuple('_sunau_params',
Expand Down Expand Up @@ -516,6 +517,14 @@ def open(f, mode=None):
mode = f.mode
else:
mode = 'rb'
try:
f = os.fspath(f)
except TypeError:
if not hasattr(f, 'read') and not hasattr(f, 'write'):
raise TypeError('open() takes str, a path-like object, '
+ 'or an open file-like object, '
+ f'not {type(f).__name__!r}') from None

if mode in ('r', 'rb'):
return Au_read(f)
elif mode in ('w', 'wb'):
Expand Down
30 changes: 30 additions & 0 deletions Lib/test/audiotests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from test.support import findfile, TESTFN, unlink
import array
import io
import pathlib
import os
from unittest import mock
import pickle

Expand Down Expand Up @@ -60,6 +62,34 @@ def test_openfp_deprecated(self):
self.module.openfp(arg, mode=mode)
mock_open.assert_called_with(arg, mode=mode)

def test_open_pathlib(self):
# test opening a pathlib to an appropriate file
fn = findfile(self.sndfilename, subdir='audiodata')
fn_as_path = pathlib.Path(fn)

with self.module.open(fn_as_path):
pass

def test_open_nonaudio_file(self):
# test opening non-audio files with appropriate errors
this_file = os.path.abspath(__file__)

with self.assertRaises(self.module.Error):
self.module.open(this_file)

this_file_as_path = pathlib.Path(this_file)
with self.assertRaises(self.module.Error):
self.module.open(this_file_as_path)

def test_open_empty_stringio(self):
this_string_io = io.StringIO()
with self.assertRaises(EOFError):
self.module.open(this_string_io)

def test_open_typeerror(self):
# open an arbitrary object without 'read' or 'write'.
with self.assertRaises(TypeError):
self.module.open(object)

class AudioWriteTests(AudioTests):

Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_aifc.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class AifcALAWTest(AifcTest, unittest.TestCase):

class AifcMiscTest(audiotests.AudioMiscTests, unittest.TestCase):
module = aifc
sndfilename = 'pluck-ulaw.aifc'

def test_skipunknown(self):
#Issue 2245
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_sunau.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class SunauULAWTest(SunauTest, unittest.TestCase):

class SunauMiscTests(audiotests.AudioMiscTests, unittest.TestCase):
module = sunau
sndfilename = 'pluck-ulaw.au'


if __name__ == "__main__":
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_wave.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class WavePCM32Test(WaveTest, unittest.TestCase):

class MiscTestCase(audiotests.AudioMiscTests, unittest.TestCase):
module = wave
sndfilename = 'pluck-pcm8.wav'

def test__all__(self):
blacklist = {'WAVE_FORMAT_PCM'}
Expand Down
9 changes: 9 additions & 0 deletions Lib/wave.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Error(Exception):
_array_fmts = None, 'b', 'h', None, 'i'

import audioop
import os
import struct
import sys
from chunk import Chunk
Expand Down Expand Up @@ -496,6 +497,14 @@ def open(f, mode=None):
mode = f.mode
else:
mode = 'rb'
try:
f = os.fspath(f)
except TypeError:
if not hasattr(f, 'read') and not hasattr(f, 'write'):
raise TypeError('open() takes str, a path-like object, '
+ 'or an open file-like object, '
+ f'not {type(f).__name__!r}') from None

if mode in ('r', 'rb'):
return Wave_read(f)
elif mode in ('w', 'wb'):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:func:`aifc.open()`, :func:`sunau.open()` and :func:`wave.open()`
now accept :term:`path-like objects <path-like object>` objects.
Patch by Michael Scott Cuthbert.