Repository: biopython/biopython

Issue:
SffIO.py incorrectly parses gzip file stream
```
import gzip
from Bio import SeqIO
with gzip.open('file.sff.gz') as fh:
  list(SeqIO.parse(fh, 'sff'))
```

Produces:

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/AMED/tyghe.vallard/Projects/usamriidPathDiscov/usamriidPathDiscov/lib/python2.6/site-packages/Bio/SeqIO/__init__.py", line 587, in parse
    yield r
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "/home/AMED/tyghe.vallard/Projects/usamriidPathDiscov/usamriidPathDiscov/lib/python2.6/site-packages/Bio/File.py", line 89, in as_handle
    yield handleish
  File "/home/AMED/tyghe.vallard/Projects/usamriidPathDiscov/usamriidPathDiscov/lib/python2.6/site-packages/Bio/SeqIO/__init__.py", line 586, in parse
    for r in i:
  File "/home/AMED/tyghe.vallard/Projects/usamriidPathDiscov/usamriidPathDiscov/lib/python2.6/site-packages/Bio/SeqIO/SffIO.py", line 887, in SffIterator
    = _sff_file_header(handle)
  File "/home/AMED/tyghe.vallard/Projects/usamriidPathDiscov/usamriidPathDiscov/lib/python2.6/site-packages/Bio/SeqIO/SffIO.py", line 280, in _sff_file_header
    if hasattr(handle, "mode") and "U" in handle.mode.upper():
AttributeError: 'int' object has no attribute 'upper'
```

https://github.com/biopython/biopython/blob/master/Bio/SeqIO/SffIO.py#L281
gzip objects have a mode, but they are integers instead of strings like file objects



Discussion:

Resolving patch:
diff --git a/Bio/SeqIO/SffIO.py b/Bio/SeqIO/SffIO.py
--- a/Bio/SeqIO/SffIO.py
+++ b/Bio/SeqIO/SffIO.py
@@ -253,6 +253,24 @@
 
 __docformat__ = "restructuredtext en"
 
+def _check_mode(handle):
+    '''
+    Ensures mode is not set for Universal new line
+    and ensures mode is binary for Windows
+    '''
+    mode = ''
+    if hasattr(handle,'mode'):
+        mode = str(handle.mode)
+
+    if mode and "U" in mode.upper():
+        raise ValueError("SFF files must NOT be opened in universal new "
+                         "lines mode. Binary mode is recommended (although "
+                         "on Unix the default mode is also fine).")
+    elif mode and "B" not in mode.upper() \
+            and sys.platform == "win32":
+        raise ValueError("SFF files must be opened in binary mode on Windows")
+
+
 def _sff_file_header(handle):
     """Read in an SFF file header (PRIVATE).
 
@@ -278,13 +296,7 @@ def _sff_file_header(handle):
     'TCAG'
 
     """
-    if hasattr(handle, "mode") and "U" in handle.mode.upper():
-        raise ValueError("SFF files must NOT be opened in universal new "
-                         "lines mode. Binary mode is recommended (although "
-                         "on Unix the default mode is also fine).")
-    elif hasattr(handle, "mode") and "B" not in handle.mode.upper() \
-            and sys.platform == "win32":
-        raise ValueError("SFF files must be opened in binary mode on Windows")
+    _check_mode(handle)
     # file header (part one)
     # use big endiean encdoing   >
     # magic_number               I
@@ -1009,11 +1021,7 @@ def __init__(self, handle, index=True, xml=None):
             - xml - Optional string argument, xml manifest to be recorded in the index
               block (see function ReadRocheXmlManifest for reading this data).
         """
-        if hasattr(handle, "mode") and "U" in handle.mode.upper():
-            raise ValueError("SFF files must NOT be opened in universal new "
-                             "lines mode. Binary mode is required")
-        elif hasattr(handle, "mode") and "B" not in handle.mode.upper():
-            raise ValueError("SFF files must be opened in binary mode")
+        _check_mode(handle)
         self.handle = handle
         self._xml = xml
         if index:


Test patch:
diff --git a/Tests/Roche/E3MFGYR02_random_10_reads.sff.gz b/Tests/Roche/E3MFGYR02_random_10_reads.sff.gz
new file mode 100644
Binary files /dev/null and b/Tests/Roche/E3MFGYR02_random_10_reads.sff.gz differ
diff --git a/Tests/test_SffIO.py b/Tests/test_SffIO.py
--- a/Tests/test_SffIO.py
+++ b/Tests/test_SffIO.py
@@ -86,6 +86,14 @@ def test_coords(self):
 
 
 class TestConcatenated(unittest.TestCase):
+    def test_parses_gzipped_stream(self):
+        import gzip
+        count = 0
+        fh = gzip.open("Roche/E3MFGYR02_random_10_reads.sff.gz",'rb')
+        for record in SeqIO.parse(fh, 'sff'):
+            count += 1
+        self.assertEqual(10, count)
+        
     def test_parse1(self):
         count = 0
         caught = False