Repository: cogent3/cogent3
Pull request #1043 (2022-05-08 22:54:50)

ENH: cogent3.load_seq() to load a single sequence from a file


Patch:
diff --git a/src/cogent3/__init__.py b/src/cogent3/__init__.py
index 5b5cc5103..8dcbac8bd 100644
--- a/src/cogent3/__init__.py
+++ b/src/cogent3/__init__.py
@@ -9,12 +9,13 @@ import pickle
 import sys
 import warnings
 
-from typing import Union
+from typing import Callable, Optional, Union
 
 from cogent3.app import available_apps
 from cogent3.core.alignment import (
     Alignment,
     ArrayAlignment,
+    Sequence,
     SequenceCollection,
 )
 from cogent3.core.genetic_code import available_codes, get_code
@@ -230,6 +231,60 @@ def _load_seqs(file_format, filename, fmt, kw, parser_kw):
     return list(FromFilenameParser(filename, fmt, **parser_kw))
 
 
+def load_seq(
+    filename: Union[str, pathlib.Path],
+    format: Optional[str] = None,
+    moltype: Optional[str] = None,
+    label_to_name: Optional[Callable] = None,
+    parser_kw: dict = None,
+    info: dict = None,
+    **kw,
+) -> Sequence:
+    """
+    loads unaligned sequences from file
+
+    Parameters
+    ----------
+    filename : str
+        path to sequence file
+    format : str
+        sequence file format, if not specified tries to guess from the path suffix
+    moltype : str
+        the moltype, eg DNA, PROTEIN, 'dna', 'protein'
+    label_to_name : callable
+        function for converting original name into another name.
+    parser_kw : dict
+        optional arguments for the parser
+    info : dict
+        a dict from which to make an info object
+    **kw
+        other keyword arguments passed to SequenceCollection
+
+    Notes
+    -----
+    Returns **one** sequence from a file. Use load_aligned_seqs or
+    load_unaligned_seqs to get a collection.
+
+    Returns
+    -------
+    ``Sequence``
+    """
+    info = info or {}
+    info["source"] = str(filename)
+    file_format, _ = get_format_suffixes(filename)
+    if file_format == "json":
+        seq = load_from_json(filename, (Sequence,))
+        seq.name = label_to_name(seq.name) if label_to_name else seq.name
+        return seq
+
+    data = _load_seqs(file_format, filename, format, kw, parser_kw)
+    name, seq = data[0]
+    name = label_to_name(name) if label_to_name else name
+    result = make_seq(seq, name, moltype=moltype)
+    result.info.update(info)
+    return result
+
+
 def load_unaligned_seqs(
     filename: Union[str, pathlib.Path],
     format=None,
diff --git a/src/cogent3/parse/cogent3_json.py b/src/cogent3/parse/cogent3_json.py
index e030987e1..9bf12047e 100644
--- a/src/cogent3/parse/cogent3_json.py
+++ b/src/cogent3/parse/cogent3_json.py
@@ -23,9 +23,10 @@ def load_from_json(filename, classes):
 
     Parameters
     ----------
-    filename: name of the json file
-    classes: A series of the Cogent3 types, for example: (Alignment, ArrayAlignment)
-
+    filename : Union[str,Path]
+        name of the json file
+    classes : Sequence[type]
+        A series of the Cogent3 types, for example: (Alignment, ArrayAlignment)
     """
     assert all(
         (isinstance(klass, type) for klass in classes)


Test patch:
diff --git a/tests/test_core/test_core_standalone.py b/tests/test_core/test_core_standalone.py
index 515a15b54..06ccf594a 100644
--- a/tests/test_core/test_core_standalone.py
+++ b/tests/test_core/test_core_standalone.py
@@ -13,6 +13,7 @@ from cogent3 import (
     RNA,
     get_format_suffixes,
     load_aligned_seqs,
+    load_seq,
     load_unaligned_seqs,
     make_aligned_seqs,
     make_seq,
@@ -99,6 +100,32 @@ class TestConstructorFunctions(unittest.TestCase):
         self.assertEqual(got.to_dict(), data)
         self.assertEqual(got.info["source"], "unknown")
 
+    def test_load_seq(self):
+        """load single sequence"""
+        from cogent3 import Sequence
+
+        paths = (
+            "c_elegans_WS199_dna_shortened.fasta",
+            "annotated_seq.gb",
+            "brca1_5.250.paml",
+        )
+        seq_names = ("I", "AE017341", "NineBande")
+        data_dir = pathlib.Path(data_path)
+        for i, path in enumerate(paths):
+            got = load_seq(data_dir / path)
+            assert isinstance(got, Sequence)
+            assert got.info.source == str(data_dir / path)
+            assert got.name == seq_names[i]
+
+        # try json
+        seq = got
+        with TemporaryDirectory(dir=".") as dirname:
+            outpath = pathlib.Path(dirname) / "seq.json"
+            outpath.write_text(seq.to_json())
+            got = load_seq(outpath)
+            assert str(got) == str(seq)
+            assert got.name == seq.name
+
     def test_load_unaligned_seqs(self):
         """test loading unaligned from file"""
         path = os.path.join(data_path, "brca1_5.paml")