summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2007-07-10 06:54:34 (GMT)
committerGuido van Rossum <guido@python.org>2007-07-10 06:54:34 (GMT)
commit7165cb1a485b1b5574785eac6e2edc8787e43c38 (patch)
tree1b8edb82abcc8ef143923a43366b6a90842b5692
parente8432ac42f9030bd75de03a07fd9059006292b7a (diff)
downloadcpython-7165cb1a485b1b5574785eac6e2edc8787e43c38.zip
cpython-7165cb1a485b1b5574785eac6e2edc8787e43c38.tar.gz
cpython-7165cb1a485b1b5574785eac6e2edc8787e43c38.tar.bz2
Made test_file pass. This meant adding support for read(-1) and read()
to even the most basic file object (I also added readall() which may be a better API). Also, not all the tests requiring specific failure modes could be saved. And there were the usual str/bytes issues. I made sure test_io.py still passes (io.py is now most thoroughly tested by combining test_file.py and test_io.py).
-rw-r--r--Lib/io.py91
-rw-r--r--Lib/test/test_file.py89
-rw-r--r--Modules/_fileio.c77
3 files changed, 158 insertions, 99 deletions
diff --git a/Lib/io.py b/Lib/io.py
index 4c9ddbb..a7cdd1f 100644
--- a/Lib/io.py
+++ b/Lib/io.py
@@ -101,7 +101,9 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None):
updating = "+" in modes
text = "t" in modes
binary = "b" in modes
- if "U" in modes and not (reading or writing or appending):
+ if "U" in modes:
+ if writing or appending:
+ raise ValueError("can't use U and writing mode at once")
reading = True
if text and binary:
raise ValueError("can't have text and binary mode at once")
@@ -296,7 +298,7 @@ class IOBase:
"""
return False
- ### Readline ###
+ ### Readline[s] and writelines ###
def readline(self, limit: int = -1) -> bytes:
"""For backwards compatibility, a (slowish) readline()."""
@@ -324,6 +326,31 @@ class IOBase:
break
return res
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ line = self.readline()
+ if not line:
+ raise StopIteration
+ return line
+
+ def readlines(self, hint=None):
+ if hint is None:
+ return list(self)
+ n = 0
+ lines = []
+ for line in self:
+ lines.append(line)
+ n += len(line)
+ if n >= hint:
+ break
+ return lines
+
+ def writelines(self, lines):
+ for line in lines:
+ self.write(line)
+
class RawIOBase(IOBase):
@@ -340,17 +367,31 @@ class RawIOBase(IOBase):
recursion in case a subclass doesn't implement either.)
"""
- def read(self, n: int) -> bytes:
+ def read(self, n: int = -1) -> bytes:
"""read(n: int) -> bytes. Read and return up to n bytes.
Returns an empty bytes array on EOF, or None if the object is
set not to block and has no data to read.
"""
+ if n is None:
+ n = -1
+ if n < 0:
+ return self.readall()
b = bytes(n.__index__())
n = self.readinto(b)
del b[n:]
return b
+ def readall(self):
+ """readall() -> bytes. Read until EOF, using multiple read() call."""
+ res = bytes()
+ while True:
+ data = self.read(DEFAULT_BUFFER_SIZE)
+ if not data:
+ break
+ res += data
+ return res
+
def readinto(self, b: bytes) -> int:
"""readinto(b: bytes) -> int. Read up to len(b) bytes into b.
@@ -494,7 +535,13 @@ class BufferedIOBase(IOBase):
# XXX This ought to work with anything that supports the buffer API
data = self.read(len(b))
n = len(data)
- b[:n] = data
+ try:
+ b[:n] = data
+ except TypeError as err:
+ import array
+ if not isinstance(b, array.array):
+ raise err
+ b[:n] = array.array('b', data)
return n
def write(self, b: bytes) -> int:
@@ -530,6 +577,8 @@ class _BufferedIOMixin(BufferedIOBase):
return self.raw.tell()
def truncate(self, pos=None):
+ if pos is None:
+ pos = self.tell()
return self.raw.truncate(pos)
### Flush and close ###
@@ -731,6 +780,9 @@ class BufferedWriter(_BufferedIOMixin):
def write(self, b):
if not isinstance(b, bytes):
+ if hasattr(b, "__index__"):
+ raise TypeError("Can't write object of type %s" %
+ type(b).__name__)
b = bytes(b)
# XXX we can implement some more tricks to try and avoid partial writes
if len(self._write_buf) > self.buffer_size:
@@ -924,42 +976,11 @@ class TextIOBase(IOBase):
"""
self._unsupported("readline")
- def __iter__(self) -> "TextIOBase": # That's a forward reference
- """__iter__() -> Iterator. Return line iterator (actually just self).
- """
- return self
-
- def __next__(self) -> str:
- """Same as readline() except raises StopIteration on immediate EOF."""
- line = self.readline()
- if not line:
- raise StopIteration
- return line
-
@property
def encoding(self):
"""Subclasses should override."""
return None
- # The following are provided for backwards compatibility
-
- def readlines(self, hint=None):
- if hint is None:
- return list(self)
- n = 0
- lines = []
- while not lines or n < hint:
- line = self.readline()
- if not line:
- break
- lines.append(line)
- n += len(line)
- return lines
-
- def writelines(self, lines):
- for line in lines:
- self.write(line)
-
class TextIOWrapper(TextIOBase):
diff --git a/Lib/test/test_file.py b/Lib/test/test_file.py
index 95e9b3e..adede2b 100644
--- a/Lib/test/test_file.py
+++ b/Lib/test/test_file.py
@@ -34,34 +34,31 @@ class AutoFileTests(unittest.TestCase):
f.mode # ditto
f.closed # ditto
- # verify the others aren't
- for attr in 'name', 'mode', 'closed':
- self.assertRaises((AttributeError, TypeError), setattr, f, attr, 'oops')
-
def testReadinto(self):
# verify readinto
self.f.write('12')
self.f.close()
- a = array('c', 'x'*10)
+ a = array('b', b'x'*10)
self.f = open(TESTFN, 'rb')
n = self.f.readinto(a)
- self.assertEquals('12', a.tostring()[:n])
+ self.assertEquals(b'12', a.tostring()[:n])
def testReadinto_text(self):
# verify readinto refuses text files
- a = array('c', 'x'*10)
+ a = array('b', b'x'*10)
self.f.close()
self.f = open(TESTFN, 'r')
- self.assertRaises(TypeError, self.f.readinto, a)
+ if hasattr(self.f, "readinto"):
+ self.assertRaises(TypeError, self.f.readinto, a)
def testWritelinesUserList(self):
# verify writelines with instance sequence
- l = UserList(['1', '2'])
+ l = UserList([b'1', b'2'])
self.f.writelines(l)
self.f.close()
self.f = open(TESTFN, 'rb')
buf = self.f.read()
- self.assertEquals(buf, '12')
+ self.assertEquals(buf, b'12')
def testWritelinesIntegers(self):
# verify writelines with integers
@@ -80,17 +77,14 @@ class AutoFileTests(unittest.TestCase):
self.assertRaises(TypeError, self.f.writelines,
[NonString(), NonString()])
- def testRepr(self):
- # verify repr works
- self.assert_(repr(self.f).startswith("<open file '" + TESTFN))
-
def testErrors(self):
f = self.f
self.assertEquals(f.name, TESTFN)
self.assert_(not f.isatty())
self.assert_(not f.closed)
- self.assertRaises(TypeError, f.readinto, "")
+ if hasattr(f, "readinto"):
+ self.assertRaises((IOError, TypeError), f.readinto, "")
f.close()
self.assert_(f.closed)
@@ -105,11 +99,11 @@ class AutoFileTests(unittest.TestCase):
self.f.__exit__(None, None, None)
self.assert_(self.f.closed)
- for methodname in methods:
- method = getattr(self.f, methodname)
- # should raise on closed file
- self.assertRaises(ValueError, method)
- self.assertRaises(ValueError, self.f.writelines, [])
+## for methodname in methods:
+## method = getattr(self.f, methodname)
+## # should raise on closed file
+## self.assertRaises(ValueError, method)
+## self.assertRaises(ValueError, self.f.writelines, [])
# file is closed, __exit__ shouldn't do anything
self.assertEquals(self.f.__exit__(None, None, None), None)
@@ -136,19 +130,12 @@ class OtherFileTests(unittest.TestCase):
def testStdin(self):
# This causes the interpreter to exit on OSF1 v5.1.
if sys.platform != 'osf1V5':
- self.assertRaises(IOError, sys.stdin.seek, -1)
+ self.assertRaises(ValueError, sys.stdin.seek, -1)
else:
print((
' Skipping sys.stdin.seek(-1), it may crash the interpreter.'
' Test manually.'), file=sys.__stdout__)
- self.assertRaises(IOError, sys.stdin.truncate)
-
- def testUnicodeOpen(self):
- # verify repr works for unicode too
- f = open(str(TESTFN), "w")
- self.assert_(repr(f).startswith("<open file u'" + TESTFN))
- f.close()
- os.unlink(TESTFN)
+ self.assertRaises(ValueError, sys.stdin.truncate)
def testBadModeArgument(self):
# verify that we get a sensible error message for bad mode argument
@@ -171,12 +158,12 @@ class OtherFileTests(unittest.TestCase):
# misbehaviour especially with repeated close() calls
for s in (-1, 0, 1, 512):
try:
- f = open(TESTFN, 'w', s)
- f.write(str(s))
+ f = open(TESTFN, 'wb', s)
+ f.write(str(s).encode("ascii"))
f.close()
f.close()
- f = open(TESTFN, 'r', s)
- d = int(f.read())
+ f = open(TESTFN, 'rb', s)
+ d = int(f.read().decode("ascii"))
f.close()
f.close()
except IOError as msg:
@@ -190,12 +177,12 @@ class OtherFileTests(unittest.TestCase):
# SF bug <http://www.python.org/sf/801631>
# "file.truncate fault on windows"
f = open(TESTFN, 'wb')
- f.write('12345678901') # 11 bytes
+ f.write(b'12345678901') # 11 bytes
f.close()
f = open(TESTFN,'rb+')
data = f.read(5)
- if data != '12345':
+ if data != b'12345':
self.fail("Read on file opened for update failed %r" % data)
if f.tell() != 5:
self.fail("File pos after read wrong %d" % f.tell())
@@ -216,28 +203,22 @@ class OtherFileTests(unittest.TestCase):
def testIteration(self):
# Test the complex interaction when mixing file-iteration and the
- # various read* methods. Ostensibly, the mixture could just be tested
- # to work when it should work according to the Python language,
- # instead of fail when it should fail according to the current CPython
- # implementation. People don't always program Python the way they
- # should, though, and the implemenation might change in subtle ways,
- # so we explicitly test for errors, too; the test will just have to
- # be updated when the implementation changes.
+ # various read* methods.
dataoffset = 16384
filler = "ham\n"
assert not dataoffset % len(filler), \
"dataoffset must be multiple of len(filler)"
nchunks = dataoffset // len(filler)
testlines = [
- "spam, spam and eggs\n",
- "eggs, spam, ham and spam\n",
- "saussages, spam, spam and eggs\n",
- "spam, ham, spam and eggs\n",
- "spam, spam, spam, spam, spam, ham, spam\n",
- "wonderful spaaaaaam.\n"
+ b"spam, spam and eggs\n",
+ b"eggs, spam, ham and spam\n",
+ b"saussages, spam, spam and eggs\n",
+ b"spam, ham, spam and eggs\n",
+ b"spam, spam, spam, spam, spam, ham, spam\n",
+ b"wonderful spaaaaaam.\n"
]
methods = [("readline", ()), ("read", ()), ("readlines", ()),
- ("readinto", (array("c", " "*100),))]
+ ("readinto", (array("b", b" "*100),))]
try:
# Prepare the testfile
@@ -251,13 +232,7 @@ class OtherFileTests(unittest.TestCase):
if next(f) != filler:
self.fail, "Broken testfile"
meth = getattr(f, methodname)
- try:
- meth(*args)
- except ValueError:
- pass
- else:
- self.fail("%s%r after next() didn't raise ValueError" %
- (methodname, args))
+ meth(*args) # This simply shouldn't fail
f.close()
# Test to see if harmless (by accident) mixing of read* and
@@ -280,7 +255,7 @@ class OtherFileTests(unittest.TestCase):
self.fail("readline() after next() with empty buffer "
"failed. Got %r, expected %r" % (line, testline))
testline = testlines.pop(0)
- buf = array("c", "\x00" * len(testline))
+ buf = array("b", b"\x00" * len(testline))
try:
f.readinto(buf)
except ValueError:
diff --git a/Modules/_fileio.c b/Modules/_fileio.c
index 76b47e5..3ca3172 100644
--- a/Modules/_fileio.c
+++ b/Modules/_fileio.c
@@ -365,11 +365,69 @@ fileio_readinto(PyFileIOObject *self, PyObject *args)
return PyInt_FromLong(n);
}
+#define DEFAULT_BUFFER_SIZE (8*1024)
+
+static PyObject *
+fileio_readall(PyFileIOObject *self)
+{
+ PyObject *result;
+ Py_ssize_t total = 0;
+ int n;
+
+ result = PyBytes_FromStringAndSize(NULL, DEFAULT_BUFFER_SIZE);
+ if (result == NULL)
+ return NULL;
+
+ while (1) {
+ Py_ssize_t newsize = total + DEFAULT_BUFFER_SIZE;
+ if (PyBytes_GET_SIZE(result) < newsize) {
+ if (PyBytes_Resize(result, newsize) < 0) {
+ if (total == 0) {
+ Py_DECREF(result);
+ return NULL;
+ }
+ PyErr_Clear();
+ break;
+ }
+ }
+ Py_BEGIN_ALLOW_THREADS
+ errno = 0;
+ n = read(self->fd,
+ PyBytes_AS_STRING(result) + total,
+ newsize - total);
+ Py_END_ALLOW_THREADS
+ if (n == 0)
+ break;
+ if (n < 0) {
+ if (total > 0)
+ break;
+ if (errno == EAGAIN) {
+ Py_DECREF(result);
+ Py_RETURN_NONE;
+ }
+ Py_DECREF(result);
+ PyErr_SetFromErrno(PyExc_IOError);
+ return NULL;
+ }
+ total += n;
+ }
+
+ if (PyBytes_GET_SIZE(result) > total) {
+ if (PyBytes_Resize(result, total) < 0) {
+ /* This should never happen, but just in case */
+ Py_DECREF(result);
+ return NULL;
+ }
+ }
+ return result;
+}
+
static PyObject *
fileio_read(PyFileIOObject *self, PyObject *args)
{
char *ptr;
- Py_ssize_t n, size;
+ Py_ssize_t n;
+ Py_ssize_t size = -1;
PyObject *bytes;
if (self->fd < 0)
@@ -377,13 +435,11 @@ fileio_read(PyFileIOObject *self, PyObject *args)
if (!self->readable)
return err_mode("reading");
- if (!PyArg_ParseTuple(args, "i", &size))
+ if (!PyArg_ParseTuple(args, "|i", &size))
return NULL;
if (size < 0) {
- PyErr_SetString(PyExc_ValueError,
- "negative read count");
- return NULL;
+ return fileio_readall(self);
}
bytes = PyBytes_FromStringAndSize(NULL, size);
@@ -624,8 +680,14 @@ PyDoc_STRVAR(read_doc,
"read(size: int) -> bytes. read at most size bytes, returned as bytes.\n"
"\n"
"Only makes one system call, so less data may be returned than requested\n"
-"In non-blocking mode, returns None if no data is available. On\n"
-"end-of-file, returns 0.");
+"In non-blocking mode, returns None if no data is available.\n"
+"On end-of-file, returns ''.");
+
+PyDoc_STRVAR(readall_doc,
+"readall() -> bytes. read all data from the file, returned as bytes.\n"
+"\n"
+"In non-blocking mode, returns as much as is immediately available,\n"
+"or None if no data is available. On end-of-file, returns ''.");
PyDoc_STRVAR(write_doc,
"write(b: bytes) -> int. Write bytes b to file, return number written.\n"
@@ -680,6 +742,7 @@ PyDoc_STRVAR(writable_doc,
static PyMethodDef fileio_methods[] = {
{"read", (PyCFunction)fileio_read, METH_VARARGS, read_doc},
+ {"readall", (PyCFunction)fileio_readall, METH_NOARGS, readall_doc},
{"readinto", (PyCFunction)fileio_readinto, METH_VARARGS, readinto_doc},
{"write", (PyCFunction)fileio_write, METH_VARARGS, write_doc},
{"seek", (PyCFunction)fileio_seek, METH_VARARGS, seek_doc},