diff options
author | Guido van Rossum <guido@python.org> | 2007-08-27 17:39:33 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 2007-08-27 17:39:33 (GMT) |
commit | 5abbf750a2b7af030ef53880f428e507bde74b62 (patch) | |
tree | 9b44c2a0e16d651f81060a0e798aab1f749871a7 | |
parent | 6dab79535139a36d7e845c1c5f399d7a59d5d630 (diff) | |
download | cpython-5abbf750a2b7af030ef53880f428e507bde74b62.zip cpython-5abbf750a2b7af030ef53880f428e507bde74b62.tar.gz cpython-5abbf750a2b7af030ef53880f428e507bde74b62.tar.bz2 |
Changes to io.py and socket.py by Christian Heimes.
- Replace all asserts by ValuleErrors or TypeErrors as appropriate.
- Add _checkReadable, _checkWritable methods; these check self.closed too.
- Add a test that everything exported by io.py exists, and is either
an exception or an IOBase instance (except for the open function).
- Default buffering to 1 if isatty() (I had to tweak this to enforce
the *default* bit -- GvR).
-rw-r--r-- | Lib/io.py | 82 | ||||
-rw-r--r-- | Lib/socket.py | 13 | ||||
-rw-r--r-- | Lib/test/test_io.py | 17 |
3 files changed, 83 insertions, 29 deletions
@@ -12,13 +12,12 @@ names like __iter__). Only the top-level names listed in the __all__ variable are part of the specification. XXX edge cases when switching between reading/writing -XXX need to default buffer size to 1 if isatty() XXX need to support 1 meaning line-buffered -XXX don't use assert to validate input requirements XXX whenever an argument is None, use the default value XXX read/write ops should check readable/writable XXX buffered readinto should work with arbitrary buffer objects XXX use incremental encoder for text output, at least for UTF-16 and UTF-8-SIG +XXX check writable, readable and seekable in appropriate places """ __author__ = ("Guido van Rossum <guido@python.org>, " @@ -26,7 +25,7 @@ __author__ = ("Guido van Rossum <guido@python.org>, " "Mark Russell <mark.russell@zen.co.uk>") __all__ = ["BlockingIOError", "open", "IOBase", "RawIOBase", "FileIO", - "SocketIO", "BytesIO", "StringIO", "BufferedIOBase", + "BytesIO", "StringIO", "BufferedIOBase", "BufferedReader", "BufferedWriter", "BufferedRWPair", "BufferedRandom", "TextIOBase", "TextIOWrapper"] @@ -38,7 +37,7 @@ import _fileio import io import warnings -# XXX Shouldn't we use st_blksize whenever we can? +# open() uses st_blksize whenever we can DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes @@ -105,11 +104,14 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None): binary stream, a buffered binary stream, or a buffered text stream, open for reading and/or writing. """ - # XXX Don't use asserts for these checks; raise TypeError or ValueError - assert isinstance(file, (basestring, int)), repr(file) - assert isinstance(mode, basestring), repr(mode) - assert buffering is None or isinstance(buffering, int), repr(buffering) - assert encoding is None or isinstance(encoding, basestring), repr(encoding) + if not isinstance(file, (basestring, int)): + raise TypeError("invalid file: %r" % file) + if not isinstance(mode, basestring): + raise TypeError("invalid mode: %r" % mode) + if buffering is not None and not isinstance(buffering, int): + raise TypeError("invalid buffering: %r" % buffering) + if encoding is not None and not isinstance(encoding, basestring): + raise TypeError("invalid encoding: %r" % encoding) modes = set(mode) if modes - set("arwb+tU") or len(mode) > len(modes): raise ValueError("invalid mode: %r" % mode) @@ -140,9 +142,10 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None): (updating and "+" or "")) if buffering is None: buffering = -1 + if buffering < 0 and raw.isatty(): + buffering = 1 if buffering < 0: buffering = DEFAULT_BUFFER_SIZE - # XXX Should default to line buffering if os.isatty(raw.fileno()) try: bs = os.fstat(raw.fileno()).st_blksize except (os.error, AttributeError): @@ -162,9 +165,10 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None): buffer = BufferedRandom(raw, buffering) elif writing or appending: buffer = BufferedWriter(raw, buffering) - else: - assert reading + elif reading: buffer = BufferedReader(raw, buffering) + else: + raise ValueError("unknown mode: %r" % mode) if binary: buffer.name = file buffer.mode = mode @@ -273,6 +277,14 @@ class IOBase(metaclass=abc.ABCMeta): """ return False + def _checkSeekable(self, msg=None): + """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) + + def readable(self) -> bool: """readable() -> bool. Return whether object was opened for reading. @@ -280,6 +292,13 @@ class IOBase(metaclass=abc.ABCMeta): """ 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: """writable() -> bool. Return whether object was opened for writing. @@ -287,6 +306,13 @@ class IOBase(metaclass=abc.ABCMeta): """ 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. @@ -295,6 +321,13 @@ class IOBase(metaclass=abc.ABCMeta): """ return self.__closed + def _checkClosed(self, msg=None): + """Internal: raise an ValueError if file is closed + """ + if self.closed: + raise ValueError("I/O operation on closed file." + if msg is None else msg) + ### Context manager ### def __enter__(self) -> "IOBase": # That's a forward reference @@ -321,8 +354,7 @@ class IOBase(metaclass=abc.ABCMeta): Returns False if we don't know. """ - if self.closed: - raise ValueError("isatty() on closed file") + self._checkClosed() return False ### Readline[s] and writelines ### @@ -354,8 +386,7 @@ class IOBase(metaclass=abc.ABCMeta): return res def __iter__(self): - if self.closed: - raise ValueError("__iter__ on closed file") + self._checkClosed() return self def __next__(self): @@ -377,8 +408,7 @@ class IOBase(metaclass=abc.ABCMeta): return lines def writelines(self, lines): - if self.closed: - raise ValueError("write to closed file") + self._checkClosed() for line in lines: self.write(line) @@ -677,7 +707,7 @@ class BufferedReader(_BufferedIOMixin): def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE): """Create a new buffered reader using the given readable raw IO object. """ - assert raw.readable() + raw._checkReadable() _BufferedIOMixin.__init__(self, raw) self._read_buf = b"" self.buffer_size = buffer_size @@ -760,7 +790,7 @@ class BufferedWriter(_BufferedIOMixin): def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None): - assert raw.writable() + raw._checkWritable() _BufferedIOMixin.__init__(self, raw) self.buffer_size = buffer_size self.max_buffer_size = (2*buffer_size @@ -842,8 +872,8 @@ class BufferedRWPair(BufferedIOBase): The arguments are two RawIO instances. """ - assert reader.readable() - assert writer.writable() + reader._checkReadable() + writer._checkWritable() self.reader = BufferedReader(reader, buffer_size) self.writer = BufferedWriter(writer, buffer_size, max_buffer_size) @@ -891,7 +921,7 @@ class BufferedRandom(BufferedWriter, BufferedReader): def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None): - assert raw.seekable() + raw._checkSeekable() BufferedReader.__init__(self, raw, buffer_size) BufferedWriter.__init__(self, raw, buffer_size, max_buffer_size) @@ -1086,7 +1116,8 @@ class TextIOWrapper(TextIOBase): return decoder def _read_chunk(self): - assert self._decoder is not None + if self._decoder is None: + raise ValueError("no decoder") if not self._telling: readahead = self.buffer.read1(self._CHUNK_SIZE) pending = self._decoder.decode(readahead, not readahead) @@ -1122,7 +1153,8 @@ class TextIOWrapper(TextIOBase): position = self.buffer.tell() decoder = self._decoder if decoder is None or self._snapshot is None: - assert self._pending == "" + if self._pending: + raise ValueError("pending data") return position decoder_state, readahead, pending = self._snapshot position -= len(readahead) diff --git a/Lib/socket.py b/Lib/socket.py index 1b3920a..bffea15 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -253,24 +253,31 @@ class SocketIO(io.RawIOBase): # XXX More docs def __init__(self, sock, mode, closer): - assert mode in ("r", "w", "rw") + if mode not in ("r", "w", "rw"): + raise ValueError("invalid mode: %r" % mode) io.RawIOBase.__init__(self) self._sock = sock self._mode = mode self._closer = closer + self._reading = "r" in mode + self._writing = "w" in mode closer.makefile_open() def readinto(self, b): + self._checkClosed() + self._checkReadable() return self._sock.recv_into(b) def write(self, b): + self._checkClosed() + self._checkWritable() return self._sock.send(b) def readable(self): - return "r" in self._mode + return self._reading and not self.closed def writable(self): - return "w" in self._mode + return self._writing and not self.closed def fileno(self): return self._sock.fileno() diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 656711f..fe2d326 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -740,11 +740,26 @@ class TextIOWrapperTest(unittest.TestCase): # XXX Tests for open() +class MiscIOTest(unittest.TestCase): + + def testImport__all__(self): + for name in io.__all__: + obj = getattr(io, name, None) + self.assert_(obj is not None, name) + if name == "open": + continue + elif "error" in name.lower(): + self.assert_(issubclass(obj, Exception), name) + else: + self.assert_(issubclass(obj, io.IOBase)) + + def test_main(): test_support.run_unittest(IOTest, BytesIOTest, StringIOTest, BufferedReaderTest, BufferedWriterTest, BufferedRWPairTest, - BufferedRandomTest, TextIOWrapperTest) + BufferedRandomTest, TextIOWrapperTest, + MiscIOTest) if __name__ == "__main__": unittest.main() |