Repository: biopython/biopython

Issue:
genbank dbsource record not exported with SeqIO.write
Hello,

When I import a genbank file using Biopython Entrez.efetch and writing it as a genbank file with SeqIO write the "DBSOURCE" record / field is not exported. When fetching the same file with the standalone Entrez Edirect tool the DBSOURCE record is present (also online; see link). The dbsource record is also present in the biopython fetched version (i.e. accessible via annotations["db_source"] or when reading the raw output). So it appears to be at the level of SeqIO write. Is this a bug?

cheers,

Almer

Link to genbank test record:

http://www.ncbi.nlm.nih.gov/protein/667652385

SeqIO.write formatted genbank file:

[test_biopython_esearch.txt](https://github.com/biopython/biopython/files/439948/test_biopython_esearch.txt)

 Test script:  

[search_ncbi_test.py.txt](https://github.com/biopython/biopython/files/439970/search_ncbi_test.py.txt)



Discussion:
I would strongly recommend you record the GenBank text from Entrez directly to disk. Biopython's SeqIO does not perfectly round-trip a GenBank/EMBL files (although this has improved slightly in the new Biopython 1.68 release).

In this case yes, the parser does capture the `DBSOURCE` line as `record.annotations['db_source']` (as a string which could be split on the semi-colons), but currently `Bio.SeqIO.write(...)` does not use this. The file which needs changing is `Bio/SeqIO/InsdcIO.py` in the `GenBankWriter` class `write_record` method.

https://github.com/biopython/biopython/blob/master/Bio/SeqIO/InsdcIO.py

This looks like an oversight - are you interested in fixing this?

Hi Peter, thanks for the fast reply and info. I think I managed to fix it. Will do some tests on some more cases.

A


Resolving patch:
diff --git a/Bio/SeqIO/InsdcIO.py b/Bio/SeqIO/InsdcIO.py
--- a/Bio/SeqIO/InsdcIO.py
+++ b/Bio/SeqIO/InsdcIO.py
@@ -1085,6 +1085,13 @@ def write_record(self, record):
             taxonomy = "."
         self._write_multi_line("", taxonomy)
 
+        if "db_source" in record.annotations:
+            # Hack around the issue of BioSQL loading a list for the db_source
+            db_source = record.annotations["db_source"]
+            if isinstance(db_source, list):
+                db_source = db_source[0]
+            self._write_single_line("DBSOURCE", db_source)
+
         if "references" in record.annotations:
             self._write_references(record)
 


Test patch:
diff --git a/Tests/common_BioSQL.py b/Tests/common_BioSQL.py
--- a/Tests/common_BioSQL.py
+++ b/Tests/common_BioSQL.py
@@ -848,7 +848,7 @@ def loop(self, filename, format):
         self.assertEqual(len(new_records), len(original_records))
         for old, new in zip(original_records, new_records):
             # TODO - remove this hack because we don't yet write these (yet):
-            for key in ["comment", "references", "db_source"]:
+            for key in ["comment", "references"]:
                 if key in old.annotations and key not in new.annotations:
                     del old.annotations[key]
             self.compare_record(old, new)
diff --git a/Tests/test_SeqIO.py b/Tests/test_SeqIO.py
--- a/Tests/test_SeqIO.py
+++ b/Tests/test_SeqIO.py
@@ -3172,6 +3172,18 @@ def test_genbank21(self):
             messages,
         )
 
+    def test_genbank22(self):
+        """Test that genbank format write doesn't destroy db_source in annotations."""
+        record = SeqIO.read("GenBank/protein_refseq.gb", "genbank")
+        db_source = record.annotations.get("db_source")
+        handle = StringIO()
+        SeqIO.write(record, handle, "genbank")
+        handle.seek(0)
+        read_record = SeqIO.read(handle, "genbank")
+        read_db_source = read_record.annotations.get("db_source")
+
+        self.assertEqual(db_source, read_db_source)
+
     def test_embl1(self):
         sequences = ["NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN...NNNNNNN"]
         ids = ["DS830848.1"]