Issue #22854: Merge UnsupportedOperation fixes from 3.5
This commit is contained in:
commit
047f3b7376
18
Lib/_pyio.py
18
Lib/_pyio.py
|
@ -390,7 +390,7 @@ class IOBase(metaclass=abc.ABCMeta):
|
||||||
def seekable(self):
|
def seekable(self):
|
||||||
"""Return a bool indicating whether object supports random access.
|
"""Return a bool indicating whether object supports random access.
|
||||||
|
|
||||||
If False, seek(), tell() and truncate() will raise UnsupportedOperation.
|
If False, seek(), tell() and truncate() will raise OSError.
|
||||||
This method may need to do a test seek().
|
This method may need to do a test seek().
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
@ -405,7 +405,7 @@ class IOBase(metaclass=abc.ABCMeta):
|
||||||
def readable(self):
|
def readable(self):
|
||||||
"""Return a bool indicating whether object was opened for reading.
|
"""Return a bool indicating whether object was opened for reading.
|
||||||
|
|
||||||
If False, read() will raise UnsupportedOperation.
|
If False, read() will raise OSError.
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -419,7 +419,7 @@ class IOBase(metaclass=abc.ABCMeta):
|
||||||
def writable(self):
|
def writable(self):
|
||||||
"""Return a bool indicating whether object was opened for writing.
|
"""Return a bool indicating whether object was opened for writing.
|
||||||
|
|
||||||
If False, write() and truncate() will raise UnsupportedOperation.
|
If False, write() and truncate() will raise OSError.
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -787,12 +787,6 @@ class _BufferedIOMixin(BufferedIOBase):
|
||||||
def seekable(self):
|
def seekable(self):
|
||||||
return self.raw.seekable()
|
return self.raw.seekable()
|
||||||
|
|
||||||
def readable(self):
|
|
||||||
return self.raw.readable()
|
|
||||||
|
|
||||||
def writable(self):
|
|
||||||
return self.raw.writable()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def raw(self):
|
def raw(self):
|
||||||
return self._raw
|
return self._raw
|
||||||
|
@ -982,6 +976,9 @@ class BufferedReader(_BufferedIOMixin):
|
||||||
self._reset_read_buf()
|
self._reset_read_buf()
|
||||||
self._read_lock = Lock()
|
self._read_lock = Lock()
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
return self.raw.readable()
|
||||||
|
|
||||||
def _reset_read_buf(self):
|
def _reset_read_buf(self):
|
||||||
self._read_buf = b""
|
self._read_buf = b""
|
||||||
self._read_pos = 0
|
self._read_pos = 0
|
||||||
|
@ -1170,6 +1167,9 @@ class BufferedWriter(_BufferedIOMixin):
|
||||||
self._write_buf = bytearray()
|
self._write_buf = bytearray()
|
||||||
self._write_lock = Lock()
|
self._write_lock = Lock()
|
||||||
|
|
||||||
|
def writable(self):
|
||||||
|
return self.raw.writable()
|
||||||
|
|
||||||
def write(self, b):
|
def write(self, b):
|
||||||
if self.closed:
|
if self.closed:
|
||||||
raise ValueError("write to closed file")
|
raise ValueError("write to closed file")
|
||||||
|
|
|
@ -203,6 +203,9 @@ class MockUnseekableIO:
|
||||||
def tell(self, *args):
|
def tell(self, *args):
|
||||||
raise self.UnsupportedOperation("not seekable")
|
raise self.UnsupportedOperation("not seekable")
|
||||||
|
|
||||||
|
def truncate(self, *args):
|
||||||
|
raise self.UnsupportedOperation("not seekable")
|
||||||
|
|
||||||
class CMockUnseekableIO(MockUnseekableIO, io.BytesIO):
|
class CMockUnseekableIO(MockUnseekableIO, io.BytesIO):
|
||||||
UnsupportedOperation = io.UnsupportedOperation
|
UnsupportedOperation = io.UnsupportedOperation
|
||||||
|
|
||||||
|
@ -361,6 +364,107 @@ class IOTest(unittest.TestCase):
|
||||||
self.assertRaises(exc, fp.seek, 1, self.SEEK_CUR)
|
self.assertRaises(exc, fp.seek, 1, self.SEEK_CUR)
|
||||||
self.assertRaises(exc, fp.seek, -1, self.SEEK_END)
|
self.assertRaises(exc, fp.seek, -1, self.SEEK_END)
|
||||||
|
|
||||||
|
def test_optional_abilities(self):
|
||||||
|
# Test for OSError when optional APIs are not supported
|
||||||
|
# The purpose of this test is to try fileno(), reading, writing and
|
||||||
|
# seeking operations with various objects that indicate they do not
|
||||||
|
# support these operations.
|
||||||
|
|
||||||
|
def pipe_reader():
|
||||||
|
[r, w] = os.pipe()
|
||||||
|
os.close(w) # So that read() is harmless
|
||||||
|
return self.FileIO(r, "r")
|
||||||
|
|
||||||
|
def pipe_writer():
|
||||||
|
[r, w] = os.pipe()
|
||||||
|
self.addCleanup(os.close, r)
|
||||||
|
# Guarantee that we can write into the pipe without blocking
|
||||||
|
thread = threading.Thread(target=os.read, args=(r, 100))
|
||||||
|
thread.start()
|
||||||
|
self.addCleanup(thread.join)
|
||||||
|
return self.FileIO(w, "w")
|
||||||
|
|
||||||
|
def buffered_reader():
|
||||||
|
return self.BufferedReader(self.MockUnseekableIO())
|
||||||
|
|
||||||
|
def buffered_writer():
|
||||||
|
return self.BufferedWriter(self.MockUnseekableIO())
|
||||||
|
|
||||||
|
def buffered_random():
|
||||||
|
return self.BufferedRandom(self.BytesIO())
|
||||||
|
|
||||||
|
def buffered_rw_pair():
|
||||||
|
return self.BufferedRWPair(self.MockUnseekableIO(),
|
||||||
|
self.MockUnseekableIO())
|
||||||
|
|
||||||
|
def text_reader():
|
||||||
|
class UnseekableReader(self.MockUnseekableIO):
|
||||||
|
writable = self.BufferedIOBase.writable
|
||||||
|
write = self.BufferedIOBase.write
|
||||||
|
return self.TextIOWrapper(UnseekableReader(), "ascii")
|
||||||
|
|
||||||
|
def text_writer():
|
||||||
|
class UnseekableWriter(self.MockUnseekableIO):
|
||||||
|
readable = self.BufferedIOBase.readable
|
||||||
|
read = self.BufferedIOBase.read
|
||||||
|
return self.TextIOWrapper(UnseekableWriter(), "ascii")
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
(pipe_reader, "fr"), (pipe_writer, "fw"),
|
||||||
|
(buffered_reader, "r"), (buffered_writer, "w"),
|
||||||
|
(buffered_random, "rws"), (buffered_rw_pair, "rw"),
|
||||||
|
(text_reader, "r"), (text_writer, "w"),
|
||||||
|
(self.BytesIO, "rws"), (self.StringIO, "rws"),
|
||||||
|
)
|
||||||
|
for [test, abilities] in tests:
|
||||||
|
if test is pipe_writer and not threading:
|
||||||
|
continue # Skip subtest that uses a background thread
|
||||||
|
with self.subTest(test), test() as obj:
|
||||||
|
readable = "r" in abilities
|
||||||
|
self.assertEqual(obj.readable(), readable)
|
||||||
|
writable = "w" in abilities
|
||||||
|
self.assertEqual(obj.writable(), writable)
|
||||||
|
seekable = "s" in abilities
|
||||||
|
self.assertEqual(obj.seekable(), seekable)
|
||||||
|
|
||||||
|
if isinstance(obj, self.TextIOBase):
|
||||||
|
data = "3"
|
||||||
|
elif isinstance(obj, (self.BufferedIOBase, self.RawIOBase)):
|
||||||
|
data = b"3"
|
||||||
|
else:
|
||||||
|
self.fail("Unknown base class")
|
||||||
|
|
||||||
|
if "f" in abilities:
|
||||||
|
obj.fileno()
|
||||||
|
else:
|
||||||
|
self.assertRaises(OSError, obj.fileno)
|
||||||
|
|
||||||
|
if readable:
|
||||||
|
obj.read(1)
|
||||||
|
obj.read()
|
||||||
|
else:
|
||||||
|
self.assertRaises(OSError, obj.read, 1)
|
||||||
|
self.assertRaises(OSError, obj.read)
|
||||||
|
|
||||||
|
if writable:
|
||||||
|
obj.write(data)
|
||||||
|
else:
|
||||||
|
self.assertRaises(OSError, obj.write, data)
|
||||||
|
|
||||||
|
if seekable:
|
||||||
|
obj.tell()
|
||||||
|
obj.seek(0)
|
||||||
|
else:
|
||||||
|
self.assertRaises(OSError, obj.tell)
|
||||||
|
self.assertRaises(OSError, obj.seek, 0)
|
||||||
|
|
||||||
|
if writable and seekable:
|
||||||
|
obj.truncate()
|
||||||
|
obj.truncate(0)
|
||||||
|
else:
|
||||||
|
self.assertRaises(OSError, obj.truncate)
|
||||||
|
self.assertRaises(OSError, obj.truncate, 0)
|
||||||
|
|
||||||
def test_open_handles_NUL_chars(self):
|
def test_open_handles_NUL_chars(self):
|
||||||
fn_with_NUL = 'foo\0bar'
|
fn_with_NUL = 'foo\0bar'
|
||||||
self.assertRaises(ValueError, self.open, fn_with_NUL, 'w')
|
self.assertRaises(ValueError, self.open, fn_with_NUL, 'w')
|
||||||
|
@ -751,12 +855,6 @@ class CommonBufferedTests:
|
||||||
|
|
||||||
self.assertEqual(42, bufio.fileno())
|
self.assertEqual(42, bufio.fileno())
|
||||||
|
|
||||||
@unittest.skip('test having existential crisis')
|
|
||||||
def test_no_fileno(self):
|
|
||||||
# XXX will we always have fileno() function? If so, kill
|
|
||||||
# this test. Else, write it.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_invalid_args(self):
|
def test_invalid_args(self):
|
||||||
rawio = self.MockRawIO()
|
rawio = self.MockRawIO()
|
||||||
bufio = self.tp(rawio)
|
bufio = self.tp(rawio)
|
||||||
|
@ -784,13 +882,9 @@ class CommonBufferedTests:
|
||||||
super().flush()
|
super().flush()
|
||||||
rawio = self.MockRawIO()
|
rawio = self.MockRawIO()
|
||||||
bufio = MyBufferedIO(rawio)
|
bufio = MyBufferedIO(rawio)
|
||||||
writable = bufio.writable()
|
|
||||||
del bufio
|
del bufio
|
||||||
support.gc_collect()
|
support.gc_collect()
|
||||||
if writable:
|
self.assertEqual(record, [1, 2, 3])
|
||||||
self.assertEqual(record, [1, 2, 3])
|
|
||||||
else:
|
|
||||||
self.assertEqual(record, [1, 2])
|
|
||||||
|
|
||||||
def test_context_manager(self):
|
def test_context_manager(self):
|
||||||
# Test usability as a context manager
|
# Test usability as a context manager
|
||||||
|
|
|
@ -237,6 +237,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #22854: Change BufferedReader.writable() and
|
||||||
|
BufferedWriter.readable() to always return False.
|
||||||
|
|
||||||
- Issue #26492: Exhausted iterator of array.array now conforms with the behavior
|
- Issue #26492: Exhausted iterator of array.array now conforms with the behavior
|
||||||
of iterators of other mutable sequences: it lefts exhausted even if iterated
|
of iterators of other mutable sequences: it lefts exhausted even if iterated
|
||||||
array is extended.
|
array is extended.
|
||||||
|
|
|
@ -2398,7 +2398,6 @@ static PyMethodDef bufferedreader_methods[] = {
|
||||||
{"close", (PyCFunction)buffered_close, METH_NOARGS},
|
{"close", (PyCFunction)buffered_close, METH_NOARGS},
|
||||||
{"seekable", (PyCFunction)buffered_seekable, METH_NOARGS},
|
{"seekable", (PyCFunction)buffered_seekable, METH_NOARGS},
|
||||||
{"readable", (PyCFunction)buffered_readable, METH_NOARGS},
|
{"readable", (PyCFunction)buffered_readable, METH_NOARGS},
|
||||||
{"writable", (PyCFunction)buffered_writable, METH_NOARGS},
|
|
||||||
{"fileno", (PyCFunction)buffered_fileno, METH_NOARGS},
|
{"fileno", (PyCFunction)buffered_fileno, METH_NOARGS},
|
||||||
{"isatty", (PyCFunction)buffered_isatty, METH_NOARGS},
|
{"isatty", (PyCFunction)buffered_isatty, METH_NOARGS},
|
||||||
{"_dealloc_warn", (PyCFunction)buffered_dealloc_warn, METH_O},
|
{"_dealloc_warn", (PyCFunction)buffered_dealloc_warn, METH_O},
|
||||||
|
@ -2489,7 +2488,6 @@ static PyMethodDef bufferedwriter_methods[] = {
|
||||||
{"close", (PyCFunction)buffered_close, METH_NOARGS},
|
{"close", (PyCFunction)buffered_close, METH_NOARGS},
|
||||||
{"detach", (PyCFunction)buffered_detach, METH_NOARGS},
|
{"detach", (PyCFunction)buffered_detach, METH_NOARGS},
|
||||||
{"seekable", (PyCFunction)buffered_seekable, METH_NOARGS},
|
{"seekable", (PyCFunction)buffered_seekable, METH_NOARGS},
|
||||||
{"readable", (PyCFunction)buffered_readable, METH_NOARGS},
|
|
||||||
{"writable", (PyCFunction)buffered_writable, METH_NOARGS},
|
{"writable", (PyCFunction)buffered_writable, METH_NOARGS},
|
||||||
{"fileno", (PyCFunction)buffered_fileno, METH_NOARGS},
|
{"fileno", (PyCFunction)buffered_fileno, METH_NOARGS},
|
||||||
{"isatty", (PyCFunction)buffered_isatty, METH_NOARGS},
|
{"isatty", (PyCFunction)buffered_isatty, METH_NOARGS},
|
||||||
|
|
|
@ -66,7 +66,7 @@ PyDoc_STRVAR(_io__IOBase_seekable__doc__,
|
||||||
"\n"
|
"\n"
|
||||||
"Return whether object supports random access.\n"
|
"Return whether object supports random access.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"If False, seek(), tell() and truncate() will raise UnsupportedOperation.\n"
|
"If False, seek(), tell() and truncate() will raise OSError.\n"
|
||||||
"This method may need to do a test seek().");
|
"This method may need to do a test seek().");
|
||||||
|
|
||||||
#define _IO__IOBASE_SEEKABLE_METHODDEF \
|
#define _IO__IOBASE_SEEKABLE_METHODDEF \
|
||||||
|
@ -87,7 +87,7 @@ PyDoc_STRVAR(_io__IOBase_readable__doc__,
|
||||||
"\n"
|
"\n"
|
||||||
"Return whether object was opened for reading.\n"
|
"Return whether object was opened for reading.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"If False, read() will raise UnsupportedOperation.");
|
"If False, read() will raise OSError.");
|
||||||
|
|
||||||
#define _IO__IOBASE_READABLE_METHODDEF \
|
#define _IO__IOBASE_READABLE_METHODDEF \
|
||||||
{"readable", (PyCFunction)_io__IOBase_readable, METH_NOARGS, _io__IOBase_readable__doc__},
|
{"readable", (PyCFunction)_io__IOBase_readable, METH_NOARGS, _io__IOBase_readable__doc__},
|
||||||
|
@ -107,7 +107,7 @@ PyDoc_STRVAR(_io__IOBase_writable__doc__,
|
||||||
"\n"
|
"\n"
|
||||||
"Return whether object was opened for writing.\n"
|
"Return whether object was opened for writing.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"If False, write() will raise UnsupportedOperation.");
|
"If False, write() will raise OSError.");
|
||||||
|
|
||||||
#define _IO__IOBASE_WRITABLE_METHODDEF \
|
#define _IO__IOBASE_WRITABLE_METHODDEF \
|
||||||
{"writable", (PyCFunction)_io__IOBase_writable, METH_NOARGS, _io__IOBase_writable__doc__},
|
{"writable", (PyCFunction)_io__IOBase_writable, METH_NOARGS, _io__IOBase_writable__doc__},
|
||||||
|
@ -127,7 +127,7 @@ PyDoc_STRVAR(_io__IOBase_fileno__doc__,
|
||||||
"\n"
|
"\n"
|
||||||
"Returns underlying file descriptor if one exists.\n"
|
"Returns underlying file descriptor if one exists.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"An IOError is raised if the IO object does not use a file descriptor.");
|
"OSError is raised if the IO object does not use a file descriptor.");
|
||||||
|
|
||||||
#define _IO__IOBASE_FILENO_METHODDEF \
|
#define _IO__IOBASE_FILENO_METHODDEF \
|
||||||
{"fileno", (PyCFunction)_io__IOBase_fileno, METH_NOARGS, _io__IOBase_fileno__doc__},
|
{"fileno", (PyCFunction)_io__IOBase_fileno, METH_NOARGS, _io__IOBase_fileno__doc__},
|
||||||
|
@ -276,4 +276,4 @@ _io__RawIOBase_readall(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
{
|
{
|
||||||
return _io__RawIOBase_readall_impl(self);
|
return _io__RawIOBase_readall_impl(self);
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=fe034152b6884e65 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=b874952f5cc248a4 input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -335,13 +335,13 @@ _io._IOBase.seekable
|
||||||
|
|
||||||
Return whether object supports random access.
|
Return whether object supports random access.
|
||||||
|
|
||||||
If False, seek(), tell() and truncate() will raise UnsupportedOperation.
|
If False, seek(), tell() and truncate() will raise OSError.
|
||||||
This method may need to do a test seek().
|
This method may need to do a test seek().
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_io__IOBase_seekable_impl(PyObject *self)
|
_io__IOBase_seekable_impl(PyObject *self)
|
||||||
/*[clinic end generated code: output=4c24c67f5f32a43d input=22676eebb81dcf1e]*/
|
/*[clinic end generated code: output=4c24c67f5f32a43d input=b976622f7fdf3063]*/
|
||||||
{
|
{
|
||||||
Py_RETURN_FALSE;
|
Py_RETURN_FALSE;
|
||||||
}
|
}
|
||||||
|
@ -368,12 +368,12 @@ _io._IOBase.readable
|
||||||
|
|
||||||
Return whether object was opened for reading.
|
Return whether object was opened for reading.
|
||||||
|
|
||||||
If False, read() will raise UnsupportedOperation.
|
If False, read() will raise OSError.
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_io__IOBase_readable_impl(PyObject *self)
|
_io__IOBase_readable_impl(PyObject *self)
|
||||||
/*[clinic end generated code: output=e48089250686388b input=12fc3d8f6be46434]*/
|
/*[clinic end generated code: output=e48089250686388b input=285b3b866a0ec35f]*/
|
||||||
{
|
{
|
||||||
Py_RETURN_FALSE;
|
Py_RETURN_FALSE;
|
||||||
}
|
}
|
||||||
|
@ -401,12 +401,12 @@ _io._IOBase.writable
|
||||||
|
|
||||||
Return whether object was opened for writing.
|
Return whether object was opened for writing.
|
||||||
|
|
||||||
If False, write() will raise UnsupportedOperation.
|
If False, write() will raise OSError.
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_io__IOBase_writable_impl(PyObject *self)
|
_io__IOBase_writable_impl(PyObject *self)
|
||||||
/*[clinic end generated code: output=406001d0985be14f input=c17a0bb6a8dfc590]*/
|
/*[clinic end generated code: output=406001d0985be14f input=9dcac18a013a05b5]*/
|
||||||
{
|
{
|
||||||
Py_RETURN_FALSE;
|
Py_RETURN_FALSE;
|
||||||
}
|
}
|
||||||
|
@ -456,12 +456,12 @@ _io._IOBase.fileno
|
||||||
|
|
||||||
Returns underlying file descriptor if one exists.
|
Returns underlying file descriptor if one exists.
|
||||||
|
|
||||||
An IOError is raised if the IO object does not use a file descriptor.
|
OSError is raised if the IO object does not use a file descriptor.
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_io__IOBase_fileno_impl(PyObject *self)
|
_io__IOBase_fileno_impl(PyObject *self)
|
||||||
/*[clinic end generated code: output=7cc0973f0f5f3b73 input=32773c5df4b7eede]*/
|
/*[clinic end generated code: output=7cc0973f0f5f3b73 input=4e37028947dc1cc8]*/
|
||||||
{
|
{
|
||||||
return iobase_unsupported("fileno");
|
return iobase_unsupported("fileno");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue