Steffen Daode Nurpmeso <sdao...@googlemail.com> added the comment:
On Thu, Apr 07, 2011 at 01:12:46AM +0000, R. David Murray wrote:
> [...] should be sufficient.
It is sufficient to fix the resource warning.
Having a completely dynamic language is a nice thing.
I would not do it like that.
Instead i would even consider to provide at least ProxyFile
publically (i.e. in I/O), because it is a nifty thing which may
be useful for a number of applications.
(PartialFile is probably too complicated in respect to SMP and FD
sharing to be public as a generic (non-abstract) class.)
I don't want to come up with a C++ lib again, but there Device-s
are doubly-linked (spinlock protected), and each has a list of
attached Stream-s, and upon program exit the list(s) is/are
walked:
open:
"IO::Device::open(): already open!!%@"
"\tI'll fail with Errno::ebusy now.%@"
close:
"IO::Device::close(): device *not* open!!%@"
"\tThis may break non-debug enabled code...%@"
"\tI'll fail with Errno::enodev now.%@"
"IO::Device::close(): error occurred while closing one%@"
"\tof the still attached streams!%@"
~Device:
"IO::Device::~Device(): still isOpen()!%@"
"\tEither subclass::close() does not call Device::close(),%@"
"\tor it's destructor does not check the open state.%@"
"\tI'll clean up anyway. Expect crashes soon.%@"
Stream somewhat likewhat. The Issues #11767, #11466, #11701, to
name a few, would never happen there if at least one test uses at
least one of the involved classes once.
Now this is not true for Python's I/O, but i think that the
interface should at least be complete and act as documented,
so saying "file-like object" implies raising of ValueError if
an object is closed etc.
Even if the class is a somewhat hidden implementation detail.
So of course hasattr() could be used instead of a special member
to test for is_open(). (I hope it's slower than the latter.)
There is no IOXY.open(), so, well, ok.
By the way, mailbox.py also has some other pitfalls as far as
i remember; i think i've even seen calls to _lock_file() for
a successful is_locked() (self._locked or so) state, which results
in unlocking because of cleanup due to lock-taking failure;
it was a shallow look only.
(That is, because: rescue me from here!!!)
I'll attach 11700.yeah.diff, which inherits from RawIOBase;
it's test adds some stuff naively.
Note this is a few-hours hack in an area i haven't touched that
much yet; it's a bit mysterious which I/O base class implements
which functions or expects which function to be present in
a subclass ... And which raises what and when.
That is - i would need to regulary review that at least once
before i would ship that out for real.
And i'll attach 11700.sigh.diff, which does only what is
necessary, following your suggestions.
Now this: choose the right diff and i'll implement #11783.
:)
----------
Added file: http://bugs.python.org/file21560/11700.yeah.diff
Added file: http://bugs.python.org/file21561/11700.sigh.diff
_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue11700>
_______________________________________
diff --git a/Lib/mailbox.py b/Lib/mailbox.py
--- a/Lib/mailbox.py
+++ b/Lib/mailbox.py
@@ -1864,40 +1864,125 @@
"""Message with MMDF-specific properties."""
-class _ProxyFile:
- """A read-only wrapper of a file."""
+class _ProxyFile(io.RawIOBase):
+ """A io.RawIOBase inheriting read-only wrapper for a seekable file.
+ It supports __iter__() and the context-manager protocol.
+ """
+ def __init__(self, file, pos=None):
+ """If pos is not None then the file will keep track of its position."""
+ self._file = file
+ self._pos = pos
+ self._trackpos = True if pos is not None else False
+ self._close = True
+ self._is_open = True
- def __init__(self, f, pos=None):
- """Initialize a _ProxyFile."""
- self._file = f
- if pos is None:
- self._pos = f.tell()
+ def _set_noclose(self):
+ """Subclass hook - use to avoid closing internal file object."""
+ self._close = False
+
+ def _closed_check(self):
+ """Raise ValueError if not open."""
+ if not self._is_open:
+ raise ValueError('I/O operation on closed file')
+
+ def close(self):
+ if self._close:
+ self._close = False
+ self._file.close()
+ del self._file
+ self._is_open = False
+
+ @property
+ def closed(self):
+ return not self._is_open
+
+ def _write_check(self):
+ """Raises io.UnsupportedOperation."""
+ raise io.UnsupportedOperation('ProxyFile is readonly')
+
+ #def fileno(self):
+ def flush(self):
+ self._closed_check()
+ self._write_check()
+
+ #def isatty(self):
+
+ def _read(self, size, read_method, add_arg=None):
+ if size < 0:
+ size = -1
+ if self._trackpos:
+ self._file.seek(self._pos)
+ if not add_arg:
+ result = read_method(size)
else:
- self._pos = pos
+ result = read_method(size, add_arg)
+ if self._trackpos:
+ self._pos = self._file.tell()
+ return result
- def read(self, size=None):
- """Read bytes."""
- return self._read(size, self._file.read)
+ def readable(self):
+ self._closed_check()
+ return self._file.readable()
- def read1(self, size=None):
- """Read bytes."""
- return self._read(size, self._file.read1)
-
- def readline(self, size=None):
- """Read a line."""
+ def readline(self, size=-1):
+ self._closed_check()
return self._read(size, self._file.readline)
- def readlines(self, sizehint=None):
- """Read multiple lines."""
+ def readlines(self, sizehint=-1):
result = []
for line in self:
result.append(line)
- if sizehint is not None:
+ if sizehint >= 0:
sizehint -= len(line)
if sizehint <= 0:
break
return result
+ def read(self, size=-1):
+ self._closed_check()
+ return self._read(size, self._file.read)
+
+ #def readall(self):
+
+ def readinto(self, by_arr):
+ self._closed_check()
+ return self._read(len(by_arr), self._file.readinto, by_arr)
+
+ def seekable(self):
+ self._closed_check()
+ return True
+
+ def seek(self, offset, whence=0):
+ self._closed_check()
+ if whence == 1:
+ if not self._trackpos:
+ self._pos = self._file.tell()
+ self._file.seek(self._pos)
+ self._pos = self._file.seek(offset, whence)
+ return self._pos
+
+ def tell(self):
+ self._closed_check()
+ if not self._trackpos:
+ self._pos = self._file.tell()
+ return self._pos
+
+ def truncate(self, size=None):
+ self._closed_check()
+ self._write_check()
+
+ def writable(self):
+ self._closed_check()
+ return False
+
+ def writelines(self, lines):
+ self._closed_check()
+ self._write_check()
+
+ def write(self, by_arr):
+ self._closed_check()
+ self._write_check()
+
def __iter__(self):
"""Iterate over lines."""
while True:
@@ -1906,32 +1991,6 @@
raise StopIteration
yield line
- def tell(self):
- """Return the position."""
- return self._pos
-
- def seek(self, offset, whence=0):
- """Change position."""
- if whence == 1:
- self._file.seek(self._pos)
- self._file.seek(offset, whence)
- self._pos = self._file.tell()
-
- def close(self):
- """Close the file."""
- if hasattr(self._file, 'close'):
- self._file.close()
- del self._file
-
- def _read(self, size, read_method):
- """Read size bytes using read_method."""
- if size is None:
- size = -1
- self._file.seek(self._pos)
- result = read_method(size)
- self._pos = self._file.tell()
- return result
-
def __enter__(self):
"""Context manager protocol support."""
return self
@@ -1939,29 +1998,16 @@
def __exit__(self, *exc):
self.close()
- def readable(self):
- return self._file.readable()
-
- def writable(self):
- return self._file.writable()
-
- def seekable(self):
- return self._file.seekable()
-
- def flush(self):
- return self._file.flush()
-
- @property
- def closed(self):
- return self._file.closed
-
class _PartialFile(_ProxyFile):
"""A read-only wrapper of part of a file."""
def __init__(self, f, start=None, stop=None):
"""Initialize a _PartialFile."""
+ if start is None:
+ start = f.tell()
_ProxyFile.__init__(self, f, start)
+ super()._set_noclose()
self._start = start
self._stop = stop
@@ -1971,6 +2017,7 @@
def seek(self, offset, whence=0):
"""Change position, possibly with respect to start or stop."""
+ self._closed_check()
if whence == 0:
self._pos = self._start
whence = 1
@@ -1988,11 +2035,6 @@
size = remaining
return _ProxyFile._read(self, size, read_method)
- def close(self):
- # do *not* close the underlying file object for partial files,
- # since it's global to the mailbox object
- del self._file
-
def _lock_file(f, dotlock=True):
"""Lock file f using lockf and dot locking."""
diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py
--- a/Lib/test/test_mailbox.py
+++ b/Lib/test/test_mailbox.py
@@ -290,12 +290,14 @@
key1 = self._box.add(_sample_message)
with self._box.get_file(key0) as file:
data0 = file.read()
- with self._box.get_file(key1) as file:
- data1 = file.read()
+ file1 = self._box.get_file(key1)
+ data1 = file1.read()
self.assertEqual(data0.decode('ascii').replace(os.linesep, '\n'),
self._template % 0)
self.assertEqual(data1.decode('ascii').replace(os.linesep, '\n'),
_sample_message)
+ file1.close()
+ file1.close()
def test_iterkeys(self):
# Get keys using iterkeys()
@@ -1833,10 +1835,35 @@
self.assertFalse(proxy.read())
def _test_close(self, proxy):
- # Close a file
+ self.assertFalse(proxy.closed)
+ self.assertTrue(proxy.seekable())
+ self.assertRaises(io.UnsupportedOperation, proxy.flush)
+ self.assertRaises(io.UnsupportedOperation, proxy.truncate, 0)
+ self.assertFalse(proxy.writable())
+ self.assertRaises(io.UnsupportedOperation, proxy.writelines, ['AU'])
+ self.assertRaises(io.UnsupportedOperation, proxy.write, 'AU')
+
proxy.close()
- self.assertRaises(AttributeError, lambda: proxy.close())
+ self.assertTrue(proxy.closed)
+ try:
+ proxy.close()
+ except:
+ self.fail('Proxy.close() failure')
+ self.assertRaises(ValueError, proxy.flush)
+ self.assertRaises(ValueError, proxy.readable)
+ self.assertRaises(ValueError, proxy.readline)
+ self.assertRaises(ValueError, proxy.readlines)
+ self.assertRaises(ValueError, proxy.read)
+ self.assertRaises(ValueError, proxy.readall)
+ self.assertRaises(ValueError, proxy.readinto, bytearray())
+ self.assertRaises(ValueError, proxy.seekable)
+ self.assertRaises(ValueError, proxy.seek, 0)
+ self.assertRaises(ValueError, proxy.tell)
+ self.assertRaises(ValueError, proxy.truncate)
+ self.assertRaises(ValueError, proxy.writable)
+ self.assertRaises(ValueError, proxy.writelines, ['AU'])
+ self.assertRaises(ValueError, proxy.write, 'AU')
class TestProxyFile(TestProxyFileBase):
diff --git a/Lib/mailbox.py b/Lib/mailbox.py
--- a/Lib/mailbox.py
+++ b/Lib/mailbox.py
@@ -1919,9 +1919,9 @@
def close(self):
"""Close the file."""
- if hasattr(self._file, 'close'):
+ if hasattr(self, '_file'):
self._file.close()
- del self._file
+ del self._file
def _read(self, size, read_method):
"""Read size bytes using read_method."""
@@ -1953,7 +1953,7 @@
@property
def closed(self):
- return self._file.closed
+ return not hasattr(self, '_file')
class _PartialFile(_ProxyFile):
@@ -1991,7 +1991,8 @@
def close(self):
# do *not* close the underlying file object for partial files,
# since it's global to the mailbox object
- del self._file
+ if hasattr(self, '_file'):
+ del self._file
def _lock_file(f, dotlock=True):
diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py
--- a/Lib/test/test_mailbox.py
+++ b/Lib/test/test_mailbox.py
@@ -1834,9 +1834,13 @@
def _test_close(self, proxy):
# Close a file
+ self.assertFalse(proxy.closed)
proxy.close()
- self.assertRaises(AttributeError, lambda: proxy.close())
-
+ self.assertTrue(proxy.closed)
+ try:
+ proxy.close()
+ except:
+ self.fail('Proxy.close() failure')
class TestProxyFile(TestProxyFileBase):
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com