Repository: cogent3/cogent3
Pull request #952 (2021-09-24 06:35:57)

bug fixes


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index 44220b961..299df7e92 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -702,11 +702,11 @@ class user_function(Composable):
         If you create a function ``foo(arg1, arg2, kwarg1=False)``. You can
         turn this into a user function, e.g.
 
-        >>> ufunc = user_function(foo, in_types, out_types, arg2val, kwarg1=True)
+        >>> ufunc = user_function(foo, in_types, out_types, arg1val, kwarg1=True)
 
         Then
 
-        >>> ufunc(val) == foo(val, arg2val, kwarg1=True)
+        >>> ufunc(arg2val) == foo(arg1val, arg2val, kwarg1=True)
         """
         super(user_function, self).__init__(
             input_types=input_types, output_types=output_types, data_types=data_types
diff --git a/src/cogent3/app/dist.py b/src/cogent3/app/dist.py
index 49cc1bec3..a5986ac7e 100644
--- a/src/cogent3/app/dist.py
+++ b/src/cogent3/app/dist.py
@@ -11,6 +11,7 @@ from .composable import (
     ALIGNED_TYPE,
     PAIRWISE_DISTANCE_TYPE,
     SERIALISABLE_TYPE,
+    TABULAR_TYPE,
     ComposableDistance,
 )
 
@@ -33,7 +34,7 @@ class fast_slow_dist(ComposableDistance):
     """
 
     _input_types = ALIGNED_TYPE
-    _output_types = (PAIRWISE_DISTANCE_TYPE, SERIALISABLE_TYPE)
+    _output_types = (PAIRWISE_DISTANCE_TYPE, TABULAR_TYPE, SERIALISABLE_TYPE)
     _data_types = ("ArrayAlignment", "Alignment")
 
     def __init__(self, distance=None, moltype=None, fast_calc=None, slow_calc=None):
@@ -121,6 +122,7 @@ class fast_slow_dist(ComposableDistance):
         else:
             empty = {p: 0 for p in itertools.product(aln.names, aln.names)}
             dists = DistanceMatrix(empty)
+        dists.source = aln.info.source
         if self._sm:
             for a in dists.template.names[0]:
                 for b in dists.template.names[1]:
diff --git a/src/cogent3/app/io.py b/src/cogent3/app/io.py
index bcc5158be..82f78b537 100644
--- a/src/cogent3/app/io.py
+++ b/src/cogent3/app/io.py
@@ -21,6 +21,7 @@ from cogent3.util.table import Table
 from .composable import (
     ALIGNED_TYPE,
     IDENTIFIER_TYPE,
+    PAIRWISE_DISTANCE_TYPE,
     SEQUENCE_TYPE,
     SERIALISABLE_TYPE,
     TABULAR_RESULT_TYPE,
@@ -331,7 +332,7 @@ class load_tabular(ComposableTabular):
 class write_tabular(_checkpointable, ComposableTabular):
     """writes tabular data"""
 
-    _input_types = (TABULAR_RESULT_TYPE, TABULAR_TYPE)
+    _input_types = (TABULAR_RESULT_TYPE, TABULAR_TYPE, PAIRWISE_DISTANCE_TYPE)
     _output_types = IDENTIFIER_TYPE
     _data_types = ("Table", "DictArray", "DistanceMatrix")
 
diff --git a/src/cogent3/core/annotation.py b/src/cogent3/core/annotation.py
index 27ad5120f..85f0b6cf6 100644
--- a/src/cogent3/core/annotation.py
+++ b/src/cogent3/core/annotation.py
@@ -289,8 +289,12 @@ class _Serialisable:
         return json.dumps(self.to_rich_dict())
 
 
+# https://pythonspeed.com/products/filmemoryprofiler/
+
+
 class _Feature(_Annotatable, _Serialisable):
     qualifier_names = ["type", "name"]
+    __slots__ = ["parent", "map", "original", "_serialisable", "base", "base_map"]
 
     def __init__(self, parent, map, original=None, **kw):
         d = locals()
diff --git a/src/cogent3/parse/paml.py b/src/cogent3/parse/paml.py
index 5b6ebc14c..c08f2814c 100644
--- a/src/cogent3/parse/paml.py
+++ b/src/cogent3/parse/paml.py
@@ -1,4 +1,6 @@
 #!/usr/bin/env python
+from io import TextIOWrapper
+
 
 __author__ = "Peter Maxwell"
 __copyright__ = "Copyright 2007-2021, The Cogent Project"
@@ -10,23 +12,32 @@ __email__ = "pm67nz@gmail.com"
 __status__ = "Production"
 
 
-def PamlParser(f):
-    d = f.readline().split()
-    numseqs, seqlen = int(d[0]), int(d[1])
-    for i in range(numseqs):
-        seqname = f.readline().strip()
-        if not seqname:
-            raise ValueError("Sequence name missing")
-        currseq = []
-        length = 0
-        while length < seqlen:
-            seq_line = f.readline()
-            if not seq_line:
-                raise ValueError(
-                    'Sequence "%s" is short: %s < %s' % (seqname, length, seqlen)
-                )
-            seq_line = seq_line.strip()
-            length += len(seq_line)
-            currseq.append(seq_line)
-
-        yield (seqname, "".join(currseq))
+def PamlParser(data):
+    if isinstance(data, TextIOWrapper):
+        data = data.read().splitlines()
+    num_seqs, seq_len = [int(v) for v in data[0].split()]
+    curr_seq = []
+    curr_length = 0
+    seqname = None
+    n = 0
+    for line in data[1:]:
+        line = line.strip()
+        if not line:
+            continue
+
+        if seqname is None:
+            seqname = line
+            continue
+
+        curr_length += len(line)
+        curr_seq.append(line)
+        if curr_length == seq_len:
+            yield seqname, "".join(curr_seq)
+
+            seqname = None
+            curr_seq = []
+            curr_length = 0
+            n += 1
+
+    if n != num_seqs:
+        raise ValueError(f"read {n} seqs, expected {num_seqs}")


Test patch:
diff --git a/tests/test_app/test_dist.py b/tests/test_app/test_dist.py
index aaeecd549..b2526083a 100644
--- a/tests/test_app/test_dist.py
+++ b/tests/test_app/test_dist.py
@@ -1,5 +1,6 @@
 import os
 
+from tempfile import TemporaryDirectory
 from unittest import TestCase, main
 
 from numpy.testing import assert_allclose
@@ -205,6 +206,27 @@ class FastSlowDistTests(TestCase):
         aligner = align.progressive_align(model="WG01", guide_tree=treestring)
         _ = aligner(self.seqs5)
 
+    def test_composes_with_write_tabular(self):
+        """correctly links to tabular"""
+        with TemporaryDirectory(dir=".") as dirname:
+            writer = io.write_tabular(dirname)
+            dist_calc = dist_app.fast_slow_dist(distance="hamming", moltype="protein")
+            _ = dist_calc + writer
+
+    def test_functions_as_composable(self):
+        """works as a composable app"""
+        from pathlib import Path
+
+        loader = io.load_aligned(moltype="dna", format="paml")
+        dist = dist_app.fast_slow_dist("hamming", moltype="dna")
+        with TemporaryDirectory(dir=".") as dirname:
+            dirname = Path(dirname)
+            writer = io.write_tabular(dirname)
+            proc = loader + dist + writer
+            _ = proc("data/brca1_5.250.paml")
+            output = dirname / "brca1_5.250.tsv"
+            self.assertTrue(output.exists())
+
 
 if __name__ == "__main__":
     main()
diff --git a/tests/test_app/test_io.py b/tests/test_app/test_io.py
index efc09fa77..28a008b25 100644
--- a/tests/test_app/test_io.py
+++ b/tests/test_app/test_io.py
@@ -122,6 +122,14 @@ class TestIo(TestCase):
         for result in results:
             self.assertIsInstance(result, ArrayAlignment)
 
+    def test_load_aligned_paml(self):
+        """should handle paml too"""
+        paml_paths = io_app.get_data_store(self.basedir, suffix="paml")
+        loader = io_app.load_aligned(format="paml")
+        results = [loader(m) for m in paml_paths]
+        for result in results:
+            self.assertIsInstance(result, ArrayAlignment)
+
     def test_load_aligned_from_zip(self):
         """correctly loads aligned seqs from a zip archive"""
 
diff --git a/tests/test_core/test_core_standalone.py b/tests/test_core/test_core_standalone.py
index 8a8ac3cb6..395cc3016 100644
--- a/tests/test_core/test_core_standalone.py
+++ b/tests/test_core/test_core_standalone.py
@@ -215,11 +215,7 @@ class ReadingWritingFileFormats(unittest.TestCase):
         aln = load_aligned_seqs(filename, **kw)
         if test_write:
             suffix, cmpr = get_format_suffixes(filename)
-            if not cmpr:
-                cmpr = ""
-            else:
-                cmpr = f".{cmpr}"
-
+            cmpr = "" if not cmpr else f".{cmpr}"
             fn = tempfile.mktemp(suffix="." + suffix + cmpr)
             aln.write(filename=fn)
             os.remove(fn)
@@ -380,9 +376,7 @@ class AlignmentTestMethods(unittest.TestCase):
         alignment = make_aligned_seqs(
             data={"seq1": "ACGTACGT", "seq2": "ACGTACGT", "seq3": "ACGTACGT"}
         )
-        result = []
-        for bit in alignment.sliding_windows(5, 2):
-            result += [bit]
+        result = [bit for bit in alignment.sliding_windows(5, 2)]
         self.assertEqual(
             result[0].to_dict(), {"seq3": "ACGTA", "seq2": "ACGTA", "seq1": "ACGTA"}
         )