Package: python-debian Version: 0.1.14ubuntu2 Severity: normal Tags: patch Currently (tested using trunk), ArFile assumes that it will always receive a filename, and that filename relates to the local filesystem. This means that, when instantiating a DebFile using only a fileobj parameter, you get something like:
>>> from debian import debfile >>> package = debfile.DebFile(fileobj=open('some-package.deb', 'r')) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python2.6/dist-packages/python_debian-_CHANGELOG_VERSION_-py2.6.egg/debian/debfile.py", line 236, in __init__ self.__version = f.read().strip() File "/usr/local/lib/python2.6/dist-packages/python_debian-_CHANGELOG_VERSION_-py2.6.egg/debian/arfile.py", line 211, in read self.__fp = open(self.__fname, "r") TypeError: coercing to Unicode: need string or buffer, NoneType found The solution I've come up with will involve DebFile sharing the fileobj among several ArFile instances (is there a way to clone file-like objects?), which means ArFiles keep an internal seek-location, and don't actually close() the file pointer (leaky?). A patch should be attached, or there is my fork on GitHub: https://github.com/Raumkraut/python-debian -- System Information: Debian Release: squeeze/sid APT prefers lucid-updates APT policy: (500, 'lucid-updates'), (500, 'lucid-security'), (500, 'lucid') Architecture: i386 (i686) Kernel: Linux 2.6.32-33-generic-pae (SMP w/2 CPU cores) Locale: LANG=en_GB.utf8, LC_CTYPE=en_GB.utf8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/dash Versions of packages python-debian depends on: ii python 2.6.5-0ubuntu1 An interactive high-level object-o ii python-apt 0.7.94.2ubuntu6.4 Python interface to libapt-pkg ii python-support 1.0.4ubuntu1 automated rebuilding support for P python-debian recommends no packages. Versions of packages python-debian suggests: ii gpgv 1.4.10-2ubuntu1 GNU privacy guard - signature veri
diff --git a/lib/debian/arfile.py b/lib/debian/arfile.py index 9ad757e..d881e5f 100644 --- a/lib/debian/arfile.py +++ b/lib/debian/arfile.py @@ -159,6 +159,7 @@ class ArMember(object): self.__fp = None # file pointer self.__offset = None # start-of-data offset self.__end = None # end-of-data offset + self.__cur = None # current seek-location (for shared fp) def from_file(fp, fname): """fp is an open File object positioned on a valid file header inside @@ -198,51 +199,68 @@ class ArMember(object): f.__fname = fname f.__offset = fp.tell() # start-of-data f.__end = f.__offset + f.__size - + + if fname is None: + f.__fp = fp + f.__cur = f.__offset + return f from_file = staticmethod(from_file) - # file interface - - # XXX this is not a sequence like file objects - def read(self, size=0): + def _init_fp(self): + """Readies the file pointer. """ if self.__fp is None: + # We have just a filename, so open it (one time) self.__fp = open(self.__fname, "r") self.__fp.seek(self.__offset) + + elif self.__fname is None: + # Nameless (possibly shared) file-like object + self.__fp.seek(self.__cur) + + def _update_cur(self): + """Update our current position. """ + self.__cur = self.__fp.tell() + + # file interface - cur = self.__fp.tell() - - if size > 0 and size <= self.__end - cur: # there's room - return self.__fp.read(size) + # XXX this is not a sequence like file objects + def read(self, size=0): + self._init_fp() - if cur >= self.__end or cur < self.__offset: - return '' + if size > 0 and size <= self.__end - self.__cur: # there's room + ret = self.__fp.read(size) - return self.__fp.read(self.__end - cur) + elif self.__cur >= self.__end or self.__cur < self.__offset: + ret = '' + + else: + ret = self.__fp.read(self.__end - self.__cur) + + self._update_cur() + return ret def readline(self, size=None): - if self.__fp is None: - self.__fp = open(self.__fname, "r") - self.__fp.seek(self.__offset) - + self._init_fp() + if size is not None: buf = self.__fp.readline(size) - if self.__fp.tell() > self.__end: + self._update_cur() + if self.__cur > self.__end: return '' return buf buf = self.__fp.readline() - if self.__fp.tell() > self.__end: + self._update_cur() + if self.__cur > self.__end: return '' else: return buf def readlines(self, sizehint=0): - if self.__fp is None: - self.__fp = open(self.__fname, "r") - self.__fp.seek(self.__offset) + self._init_fp() buf = None lines = [] @@ -255,11 +273,9 @@ class ArMember(object): return lines def seek(self, offset, whence=0): - if self.__fp is None: - self.__fp = open(self.__fname, "r") - self.__fp.seek(self.__offset) + self._init_fp() - if self.__fp.tell() < self.__offset: + if self.__cur < self.__offset: self.__fp.seek(self.__offset) if whence < 2 and offset + self.__fp.tell() < self.__offset: @@ -271,20 +287,21 @@ class ArMember(object): self.__fp.seek(self.__offset + offset, 0) elif whence == 2: self.__fp.seek(self.__end + offset, 0) + + self._update_cur() def tell(self): - if self.__fp is None: - self.__fp = open(self.__fname, "r") - self.__fp.seek(self.__offset) + self._init_fp() - cur = self.__fp.tell() - - if cur < self.__offset: + if self.__cur < self.__offset: return 0L else: - return cur - self.__offset + return self.__cur - self.__offset def close(self): + if self.__fname is None: + # NB. self.__fp might be shared. Leaky to never explicitly close? + return if self.__fp is not None: self.__fp.close() diff --git a/tests/test_debfile.py b/tests/test_debfile.py index b37dfd7..1385cc4 100755 --- a/tests/test_debfile.py +++ b/tests/test_debfile.py @@ -98,6 +98,11 @@ class TestArFile(unittest.TestCase): m.close() f.close() +class TestArFileObj(TestArFile): + def setUp(self): + TestArFile.setUp(self) + self.a = arfile.ArFile(fileobj=open("test.ar", "r")) + class TestDebFile(unittest.TestCase): def setUp(self):