Index: Lib/_pyio.py =================================================================== --- Lib/_pyio.py (revision 71461) +++ Lib/_pyio.py (working copy) @@ -367,13 +367,6 @@ """ return False - def _checkReadable(self, msg=None): - """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) - def writable(self) -> bool: """Return whether object was opened for writing. @@ -381,13 +374,6 @@ """ return False - def _checkWritable(self, msg=None): - """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) - @property def closed(self): """closed: bool. True iff the file has been closed. @@ -838,7 +824,10 @@ def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE): """Create a new buffered reader using the given readable raw IO object. """ - raw._checkReadable() + + if not raw.readable(): + raise IOError('"raw" argument must be readable.') + _BufferedIOMixin.__init__(self, raw) if buffer_size <= 0: raise ValueError("invalid buffer size") @@ -969,7 +958,10 @@ def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None): - raw._checkWritable() + + if not raw.writable(): + raise IOError('"raw" argument must be writable.') + _BufferedIOMixin.__init__(self, raw) if buffer_size <= 0: raise ValueError("invalid buffer size") @@ -1075,8 +1067,13 @@ """ if max_buffer_size is not None: warnings.warn("max_buffer_size is deprecated", DeprecationWarning, 2) - reader._checkReadable() - writer._checkWritable() + + if not reader.readable(): + raise IOError('"reader" argument must be readable.') + + if not writer.writable(): + raise IOError('"writer" argument must be writable.') + self.reader = BufferedReader(reader, buffer_size) self.writer = BufferedWriter(writer, buffer_size) @@ -1697,7 +1694,9 @@ return cookie def read(self, n=None): - self._checkReadable() + if not self.readable(): + raise IOError("File or stream is not readable.") + if n is None: n = -1 decoder = self._decoder or self._get_decoder() Index: Lib/test/test_io.py =================================================================== --- Lib/test/test_io.py (revision 71461) +++ Lib/test/test_io.py (working copy) @@ -1051,13 +1051,11 @@ class BufferedRWPairTest(unittest.TestCase): - def test_basic(self): - r = self.MockRawIO(()) - w = self.MockRawIO() - pair = self.tp(r, w) + def test_constructor(self): + pair = self.tp(self.MockRawIO(), self.MockRawIO()) self.assertFalse(pair.closed) - def test_max_buffer_size_deprecation(self): + def test_constructor_max_buffer_size_deprecation(self): with support.check_warnings() as w: warnings.simplefilter("always", DeprecationWarning) self.tp(self.MockRawIO(), self.MockRawIO(), 8, 12) @@ -1067,8 +1065,95 @@ self.assertEqual(str(warning.message), "max_buffer_size is deprecated") - # XXX More Tests + def test_constructor_with_not_readable(self): + class NotReadable(MockRawIO): + def readable(self): + return False + self.assertRaises(IOError, self.tp, NotReadable(), self.MockRawIO()) + + def test_constructor_with_not_writeable(self): + class NotWriteable(MockRawIO): + def writable(self): + return False + + self.assertRaises(IOError, self.tp, self.MockRawIO(), NotWriteable()) + + def test_read(self): + pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) + + self.assertEqual(pair.read(3), b"abc") + self.assertEqual(pair.read(1), b"d") + self.assertEqual(pair.read(), b"ef") + + def test_read1(self): + # .read1() is delegated to the underlying reader object, so this test + # can be shallow. + pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) + + self.assertEqual(pair.read1(3), b"abc") + + def test_readinto(self): + pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) + + data = bytearray(5) + self.assertEqual(pair.readinto(data), 5) + self.assertEqual(data, b"abcde") + + def test_write(self): + w = self.MockRawIO() + pair = self.tp(self.MockRawIO(), w) + + pair.write(b"abc") + pair.flush() + pair.write(b"def") + pair.flush() + self.assertEqual(w._write_stack, [b"abc", b"def"]) + + def test_peek(self): + pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) + + self.assertTrue(pair.peek(3).startswith(b"abc")) + self.assertEqual(pair.read(3), b"abc") + + def test_readable(self): + pair = self.tp(self.MockRawIO(), self.MockRawIO()) + self.assertTrue(pair.readable) + + def test_writeable(self): + pair = self.tp(self.MockRawIO(), self.MockRawIO()) + self.assertTrue(pair.writable) + + # .flush() is delegated to the underlying writer object and has been + # tested in the test_write method. + + def test_close_and_closed(self): + pair = self.tp(self.MockRawIO(), self.MockRawIO()) + self.assertFalse(pair.closed) + pair.close() + self.assertTrue(pair.closed) + + def test_isatty(self): + class SelectableIsAtty(MockRawIO): + def __init__(self, isatty): + MockRawIO.__init__(self) + self._isatty = isatty + + def isatty(self): + return self._isatty + + pair = self.tp(SelectableIsAtty(False), SelectableIsAtty(False)) + self.assertFalse(pair.isatty()) + + pair = self.tp(SelectableIsAtty(True), SelectableIsAtty(False)) + self.assertTrue(pair.isatty()) + + pair = self.tp(SelectableIsAtty(False), SelectableIsAtty(True)) + self.assertTrue(pair.isatty()) + + pair = self.tp(SelectableIsAtty(True), SelectableIsAtty(True)) + self.assertTrue(pair.isatty()) + class CBufferedRWPairTest(BufferedRWPairTest): tp = io.BufferedRWPair Index: Modules/_io/bufferedio.c =================================================================== --- Modules/_io/bufferedio.c (revision 71461) +++ Modules/_io/bufferedio.c (working copy) @@ -1799,7 +1799,7 @@ static int BufferedRWPair_init(BufferedRWPairObject *self, PyObject *args, - PyObject *kwds) + PyObject *kwds) { PyObject *reader, *writer; Py_ssize_t buffer_size = DEFAULT_BUFFER_SIZE; @@ -1818,29 +1818,18 @@ if (_PyIOBase_checkWritable(writer, Py_True) == NULL) return -1; - args = Py_BuildValue("(n)", buffer_size); - if (args == NULL) { - Py_CLEAR(self->reader); - return -1; - } - self->reader = (BufferedObject *)PyType_GenericNew( - &PyBufferedReader_Type, args, NULL); - Py_DECREF(args); + self->reader = (BufferedObject *) PyObject_CallFunction( + (PyObject *) &PyBufferedReader_Type, "On", reader, buffer_size); if (self->reader == NULL) return -1; - args = Py_BuildValue("(n)", buffer_size); - if (args == NULL) { - Py_CLEAR(self->reader); - return -1; - } - self->writer = (BufferedObject *)PyType_GenericNew( - &PyBufferedWriter_Type, args, NULL); - Py_DECREF(args); + self->writer = (BufferedObject *) PyObject_CallFunction( + (PyObject *) &PyBufferedWriter_Type, "On", writer, buffer_size); if (self->writer == NULL) { - Py_CLEAR(self->reader); + Py_DECREF(self->reader); return -1; } + return 0; } @@ -1905,6 +1894,12 @@ } static PyObject * +BufferedRWPair_readinto(BufferedRWPairObject *self, PyObject *args) +{ + return _forward_call(self->reader, "readinto", args); +} + +static PyObject * BufferedRWPair_write(BufferedRWPairObject *self, PyObject *args) { return _forward_call(self->writer, "write", args); @@ -1953,12 +1948,17 @@ return _forward_call(self->reader, "isatty", args); } +static PyObject * +BufferedRWPair_closed_get(BufferedRWPairObject *self, void *context) +{ + return PyObject_GetAttr((PyObject *) self->writer, _PyIO_str_closed); +} static PyMethodDef BufferedRWPair_methods[] = { {"read", (PyCFunction)BufferedRWPair_read, METH_VARARGS}, {"peek", (PyCFunction)BufferedRWPair_peek, METH_VARARGS}, {"read1", (PyCFunction)BufferedRWPair_read1, METH_VARARGS}, - {"readinto", (PyCFunction)Buffered_readinto, METH_VARARGS}, + {"readinto", (PyCFunction)BufferedRWPair_readinto, METH_VARARGS}, {"write", (PyCFunction)BufferedRWPair_write, METH_VARARGS}, {"flush", (PyCFunction)BufferedRWPair_flush, METH_NOARGS}, @@ -1972,6 +1972,11 @@ {NULL, NULL} }; +static PyGetSetDef BufferedRWPair_getset[] = { + {"closed", (getter)BufferedRWPair_closed_get, NULL, NULL}, + {0} +}; + PyTypeObject PyBufferedRWPair_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_io.BufferedRWPair", /*tp_name*/ @@ -2003,7 +2008,7 @@ 0, /* tp_iternext */ BufferedRWPair_methods, /* tp_methods */ 0, /* tp_members */ - 0, /* tp_getset */ + BufferedRWPair_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */