Issue #9293: I/O streams now raise `io.UnsupportedOperation` when an

unsupported operation is attempted (for example, writing to a file open
only for reading).
This commit is contained in:
Antoine Pitrou 2010-09-05 23:01:12 +00:00
parent bad092556e
commit 0d739d7047
6 changed files with 82 additions and 41 deletions

View File

@ -243,8 +243,13 @@ class OpenWrapper:
return open(*args, **kwargs)
class UnsupportedOperation(ValueError, IOError):
pass
# In normal operation, both `UnsupportedOperation`s should be bound to the
# same object.
try:
UnsupportedOperation = io.UnsupportedOperation
except AttributeError:
class UnsupportedOperation(ValueError, IOError):
pass
class IOBase(metaclass=abc.ABCMeta):
@ -362,9 +367,8 @@ class IOBase(metaclass=abc.ABCMeta):
"""Internal: raise an IOError if file is not seekable
"""
if not self.seekable():
raise IOError("File or stream is not seekable."
if msg is None else msg)
raise UnsupportedOperation("File or stream is not seekable."
if msg is None else msg)
def readable(self) -> bool:
"""Return whether object was opened for reading.
@ -377,8 +381,8 @@ class IOBase(metaclass=abc.ABCMeta):
"""Internal: raise an IOError if file is not readable
"""
if not self.readable():
raise IOError("File or stream is not readable."
if msg is None else msg)
raise UnsupportedOperation("File or stream is not readable."
if msg is None else msg)
def writable(self) -> bool:
"""Return whether object was opened for writing.
@ -391,8 +395,8 @@ class IOBase(metaclass=abc.ABCMeta):
"""Internal: raise an IOError if file is not writable
"""
if not self.writable():
raise IOError("File or stream is not writable."
if msg is None else msg)
raise UnsupportedOperation("File or stream is not writable."
if msg is None else msg)
@property
def closed(self):
@ -1647,7 +1651,7 @@ class TextIOWrapper(TextIOBase):
def tell(self):
if not self._seekable:
raise IOError("underlying stream is not seekable")
raise UnsupportedOperation("underlying stream is not seekable")
if not self._telling:
raise IOError("telling position disabled by next() call")
self.flush()
@ -1726,17 +1730,17 @@ class TextIOWrapper(TextIOBase):
if self.closed:
raise ValueError("tell on closed file")
if not self._seekable:
raise IOError("underlying stream is not seekable")
raise UnsupportedOperation("underlying stream is not seekable")
if whence == 1: # seek relative to current position
if cookie != 0:
raise IOError("can't do nonzero cur-relative seeks")
raise UnsupportedOperation("can't do nonzero cur-relative seeks")
# Seeking to the current position should attempt to
# sync the underlying buffer with the current position.
whence = 0
cookie = self.tell()
if whence == 2: # seek relative to end of file
if cookie != 0:
raise IOError("can't do nonzero end-relative seeks")
raise UnsupportedOperation("can't do nonzero end-relative seeks")
self.flush()
position = self.buffer.seek(0, 2)
self._set_decoded_chars('')

View File

@ -179,6 +179,23 @@ class PyMockFileIO(MockFileIO, pyio.BytesIO):
pass
class MockUnseekableIO:
def seekable(self):
return False
def seek(self, *args):
raise self.UnsupportedOperation("not seekable")
def tell(self, *args):
raise self.UnsupportedOperation("not seekable")
class CMockUnseekableIO(MockUnseekableIO, io.BytesIO):
UnsupportedOperation = io.UnsupportedOperation
class PyMockUnseekableIO(MockUnseekableIO, pyio.BytesIO):
UnsupportedOperation = pyio.UnsupportedOperation
class MockNonBlockWriterIO:
def __init__(self):
@ -304,16 +321,26 @@ class IOTest(unittest.TestCase):
def test_invalid_operations(self):
# Try writing on a file opened in read mode and vice-versa.
exc = self.UnsupportedOperation
for mode in ("w", "wb"):
with self.open(support.TESTFN, mode) as fp:
self.assertRaises(IOError, fp.read)
self.assertRaises(IOError, fp.readline)
self.assertRaises(exc, fp.read)
self.assertRaises(exc, fp.readline)
with self.open(support.TESTFN, "wb", buffering=0) as fp:
self.assertRaises(exc, fp.read)
self.assertRaises(exc, fp.readline)
with self.open(support.TESTFN, "rb", buffering=0) as fp:
self.assertRaises(exc, fp.write, b"blah")
self.assertRaises(exc, fp.writelines, [b"blah\n"])
with self.open(support.TESTFN, "rb") as fp:
self.assertRaises(IOError, fp.write, b"blah")
self.assertRaises(IOError, fp.writelines, [b"blah\n"])
self.assertRaises(exc, fp.write, b"blah")
self.assertRaises(exc, fp.writelines, [b"blah\n"])
with self.open(support.TESTFN, "r") as fp:
self.assertRaises(IOError, fp.write, "blah")
self.assertRaises(IOError, fp.writelines, ["blah\n"])
self.assertRaises(exc, fp.write, "blah")
self.assertRaises(exc, fp.writelines, ["blah\n"])
# Non-zero seeking from current or end pos
self.assertRaises(exc, fp.seek, 1, self.SEEK_CUR)
self.assertRaises(exc, fp.seek, -1, self.SEEK_END)
def test_raw_file_io(self):
with self.open(support.TESTFN, "wb", buffering=0) as f:
@ -670,6 +697,11 @@ class CommonBufferedTests:
b.close()
self.assertRaises(ValueError, b.flush)
def test_unseekable(self):
bufio = self.tp(self.MockUnseekableIO(b"A" * 10))
self.assertRaises(self.UnsupportedOperation, bufio.tell)
self.assertRaises(self.UnsupportedOperation, bufio.seek, 0)
class BufferedReaderTest(unittest.TestCase, CommonBufferedTests):
read_mode = "rb"
@ -1433,6 +1465,9 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest):
BufferedReaderTest.test_misbehaved_io(self)
BufferedWriterTest.test_misbehaved_io(self)
# You can't construct a BufferedRandom over a non-seekable stream.
test_unseekable = None
class CBufferedRandomTest(BufferedRandomTest):
tp = io.BufferedRandom
@ -2177,6 +2212,11 @@ class TextIOWrapperTest(unittest.TestCase):
txt.close()
self.assertRaises(ValueError, txt.flush)
def test_unseekable(self):
txt = self.TextIOWrapper(self.MockUnseekableIO(self.testdata))
self.assertRaises(self.UnsupportedOperation, txt.tell)
self.assertRaises(self.UnsupportedOperation, txt.seek, 0)
class CTextIOWrapperTest(TextIOWrapperTest):
def test_initialization(self):
@ -2550,7 +2590,7 @@ def test_main():
# Put the namespaces of the IO module we are testing and some useful mock
# classes in the __dict__ of each test.
mocks = (MockRawIO, MisbehavedRawIO, MockFileIO, CloseFailureIO,
MockNonBlockWriterIO)
MockNonBlockWriterIO, MockUnseekableIO)
all_members = io.__all__ + ["IncrementalNewlineDecoder"]
c_io_ns = {name : getattr(io, name) for name in all_members}
py_io_ns = {name : getattr(pyio, name) for name in all_members}

View File

@ -13,6 +13,10 @@ Core and Builtins
Library
-------
- Issue #9293: I/O streams now raise ``io.UnsupportedOperation`` when an
unsupported operation is attempted (for example, writing to a file open
only for reading).
What's New in Python 3.2 Alpha 2?
=================================

View File

@ -417,7 +417,8 @@ err_closed(void)
static PyObject *
err_mode(char *action)
{
PyErr_Format(PyExc_ValueError, "File not open for %s", action);
PyErr_Format(IO_STATE->unsupported_operation,
"File not open for %s", action);
return NULL;
}

View File

@ -317,7 +317,7 @@ _PyIOBase_check_seekable(PyObject *self, PyObject *args)
return NULL;
if (res != Py_True) {
Py_CLEAR(res);
PyErr_SetString(PyExc_IOError, "File or stream is not seekable.");
iobase_unsupported("File or stream is not seekable.");
return NULL;
}
if (args == Py_True) {
@ -346,7 +346,7 @@ _PyIOBase_check_readable(PyObject *self, PyObject *args)
return NULL;
if (res != Py_True) {
Py_CLEAR(res);
PyErr_SetString(PyExc_IOError, "File or stream is not readable.");
iobase_unsupported("File or stream is not readable.");
return NULL;
}
if (args == Py_True) {
@ -375,7 +375,7 @@ _PyIOBase_check_writable(PyObject *self, PyObject *args)
return NULL;
if (res != Py_True) {
Py_CLEAR(res);
PyErr_SetString(PyExc_IOError, "File or stream is not writable.");
iobase_unsupported("File or stream is not writable.");
return NULL;
}
if (args == Py_True) {

View File

@ -1259,10 +1259,8 @@ textiowrapper_write(textio *self, PyObject *args)
CHECK_CLOSED(self);
if (self->encoder == NULL) {
PyErr_SetString(PyExc_IOError, "not writable");
return NULL;
}
if (self->encoder == NULL)
return _unsupported("not writable");
Py_INCREF(text);
@ -1399,7 +1397,7 @@ textiowrapper_read_chunk(textio *self)
*/
if (self->decoder == NULL) {
PyErr_SetString(PyExc_IOError, "not readable");
_unsupported("not readable");
return -1;
}
@ -1489,10 +1487,8 @@ textiowrapper_read(textio *self, PyObject *args)
CHECK_CLOSED(self);
if (self->decoder == NULL) {
PyErr_SetString(PyExc_IOError, "not readable");
return NULL;
}
if (self->decoder == NULL)
return _unsupported("not readable");
if (_textiowrapper_writeflush(self) < 0)
return NULL;
@ -1983,8 +1979,7 @@ textiowrapper_seek(textio *self, PyObject *args)
Py_INCREF(cookieObj);
if (!self->seekable) {
PyErr_SetString(PyExc_IOError,
"underlying stream is not seekable");
_unsupported("underlying stream is not seekable");
goto fail;
}
@ -1995,8 +1990,7 @@ textiowrapper_seek(textio *self, PyObject *args)
goto fail;
if (cmp == 0) {
PyErr_SetString(PyExc_IOError,
"can't do nonzero cur-relative seeks");
_unsupported("can't do nonzero cur-relative seeks");
goto fail;
}
@ -2016,8 +2010,7 @@ textiowrapper_seek(textio *self, PyObject *args)
goto fail;
if (cmp == 0) {
PyErr_SetString(PyExc_IOError,
"can't do nonzero end-relative seeks");
_unsupported("can't do nonzero end-relative seeks");
goto fail;
}
@ -2151,8 +2144,7 @@ textiowrapper_tell(textio *self, PyObject *args)
CHECK_CLOSED(self);
if (!self->seekable) {
PyErr_SetString(PyExc_IOError,
"underlying stream is not seekable");
_unsupported("underlying stream is not seekable");
goto fail;
}
if (!self->telling) {