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):

Reply via email to