Repository: cogent3/cogent3
Pull request #1366 (2023-04-17 02:05:24)

Mis changes


Patch:
diff --git a/changelog.md b/changelog.md
index 7db357e57..4c94a87b0 100755
--- a/changelog.md
+++ b/changelog.md
@@ -19,16 +19,16 @@ Thanks to dgslos who raised the issue regarding IUPAC consensus. Thanks to users
 ### Composable apps
 
 - app_help() and get_app() available as top-level imports.
-     - app_help() takes the name of the app as a string and displays its summary, parameters and how to create one using get_app().
-     - get_app() creates an app instance given its name as a string and constructor arguments.
+   - app_help() takes the name of the app as a string and displays its summary, parameters and how to create one using get_app().
+   - get_app() creates an app instance given its name as a string and constructor arguments.
 - added skip_not_completed keyword parameter to define_app decorator.
-    - Some apps need to process NotCompleted instances. The current `app.__call__` method returns these instances immediately without passing through to the apps `.main()` method. The change introduces a semi-private attribute `_skip_not_completed` to the class. If it's False, the instance will be passed to `main()`.
+   - Some apps need to process NotCompleted instances. The current `app.__call__` method returns these instances immediately without passing through to the apps `.main()` method. The change introduces a semi-private attribute `_skip_not_completed` to the class. If it's False, the instance will be passed to `main()`.
 - composable data validation now allows NotCompleted
-    - if <app>.input returned a NotCompleted, it was being treated as an invalid data type rather than preserving the original cause for failure. The data validation method now immediately returns a provided NotCompleted instance
+   - if <app>.input returned a NotCompleted, it was being treated as an invalid data type rather than preserving the original cause for failure. The data validation method now immediately returns a provided NotCompleted instance
 - add argument id_from_source to all writer apps for a naming callback
-    - It should be a callable that generates a unique ID from input data
-    - Defaults to new get_unique_id() function, which extracts base name by calling get_data_source() and processing the result, removing file suffixes identified by get_format_suffixes().
-    - this means filename suffixes are dropped more cleanly
+  - It should be a callable that generates a unique ID from input data
+  - Defaults to new get_unique_id() function, which extracts base name by calling get_data_source() and processing the result, removing file suffixes identified by get_format_suffixes().
+  - this means filename suffixes are dropped more cleanly
 - new app to_primitive(). This uses a to_rich_dict() method if available otherwise it just returns the original object.
 - new app from_primitive(). This takes a dict and deserialises the object using the standard cogent3 functions.
 - new app pickle_it(). Does as the name implies.
@@ -41,8 +41,8 @@ Thanks to dgslos who raised the issue regarding IUPAC consensus. Thanks to users
   - these are to_primitive() + pickle_it() (and the reverse)
 - app.typing.get_constraint_names() now supports all standard python Sequence built-ins (list, tuple, set).
 - add type resolver for nested types
-    - function resolves the type tree of nested types and also returns the depth of that type tree
-    - ensure custom apps don't have excessive nested types. The motivation for this check is it is difficult to efficiently resolve, so we advise the developer (via a TypeError message) to define a custom class for such complex types. They can then choose to validate construction of those class attributes themselves.
+  - function resolves the type tree of nested types and also returns the depth of that type tree
+  - ensure custom apps don't have excessive nested types. The motivation for this check is it is difficult to efficiently resolve, so we advise the developer (via a TypeError message) to define a custom class for such complex types. They can then choose to validate construction of those class attributes themselves.
 
 ### DataStores
 
@@ -160,17 +160,17 @@ We will drop support for python 3.7 by release 2022.10.
 ## ENH
 
 - new `cogent3.util.parallel.as_completed()` generator function
-    - `as_completed()` wraps MPI or `concurrent.futures` executors and delivers results as they are completed. In contrast, `parallel.imap()` / `parallel.map()` deliver results in the same order as the input series. The advantage of `as_completed()` is the interval of result arrival at the parent process is better distributed.
+  - `as_completed()` wraps MPI or `concurrent.futures` executors and delivers results as they are completed. In contrast, `parallel.imap()` / `parallel.map()` deliver results in the same order as the input series. The advantage of `as_completed()` is the interval of result arrival at the parent process is better distributed.
 - new function `cogent3.load_seq()` loads a single sequence from a file
 - convert substitution model `__str__` to `__repr__`; more useful since `__repr__` is called also by str().
 
 ## BUG
 
 - fixes to `annotation_from_gff()` method on annotatable sequence / alignment objects
-    - method would break if GFF records had no ID. This situation is quite common in some Ensembl gff3 files. We generate a "no-id-#" identifier in those cases.
-    - we now add features are added to their parent feature.
+  - method would break if GFF records had no ID. This situation is quite common in some Ensembl gff3 files. We generate a "no-id-#" identifier in those cases.
+  - we now add features are added to their parent feature.
 - improve consistency in setting motif_probs on likelihood function
-    - only apply a pseudocount if optimising motif probs and at least one state has zero frequency, default pseudocount is 0.5. Thanks to StephenRogers1 for finding this issue!
+  - only apply a pseudocount if optimising motif probs and at least one state has zero frequency, default pseudocount is 0.5. Thanks to StephenRogers1 for finding this issue!
 
 ## DOC
 
@@ -289,4 +289,4 @@ We will drop support for python 3.7 by release 2022.10.
 
 - More robust alignment to reference algorithm. Builds a multiple sequence alignment from a series of pairwise alignments to a reference sequence. cogent3.app.align.align_to_ref() now retains gaps in the reference. This will be modestly slower than previously, but avoids losing information if the choice of reference sequence is a bad one.
 - cogent3.app.composable.appify decorator class, simplifies converting a user defined function into a cogent3 composable app
-- JSD calculation now uses more accurate math.fsum()
\ No newline at end of file
+- JSD calculation now uses more accurate math.fsum()
diff --git a/src/cogent3/__init__.py b/src/cogent3/__init__.py
index e7d00edbe..6f9dbf337 100644
--- a/src/cogent3/__init__.py
+++ b/src/cogent3/__init__.py
@@ -25,8 +25,6 @@ from cogent3.core.moltype import (
     DNA,
     PROTEIN,
     RNA,
-    STANDARD_CODON,
-    CodonAlphabet,
     available_moltypes,
     get_moltype,
 )
diff --git a/src/cogent3/core/genetic_code.py b/src/cogent3/core/genetic_code.py
index dab09dd2e..a63ae0ca6 100644
--- a/src/cogent3/core/genetic_code.py
+++ b/src/cogent3/core/genetic_code.py
@@ -346,6 +346,13 @@ class GeneticCode:
 
         return "".join(mappings)
 
+    def get_alphabet(self, include_stop=False):
+        from cogent3.core.alphabet import Alphabet
+        from cogent3.core.moltype import DNA
+
+        motifs = list(self.codons) if include_stop else list(self.sense_codons)
+        return Alphabet(motifs, moltype=DNA)
+
 
 NcbiGeneticCodeData = [
     GeneticCode(*data)
diff --git a/src/cogent3/core/moltype.py b/src/cogent3/core/moltype.py
index 99328fc46..dd0bef8b7 100644
--- a/src/cogent3/core/moltype.py
+++ b/src/cogent3/core/moltype.py
@@ -1380,6 +1380,10 @@ AB = MolType(
     label="ab",
 )
 
+# todo the _CodonAlphabet class should not exist,
+# the genetic code should have an alphabet, not
+# the alphabet has a genetic code
+
 
 class _CodonAlphabet(Alphabet):
     """Codon alphabets are DNA TupleAlphabets with a genetic code attribute and some codon-specific methods"""
@@ -1413,15 +1417,9 @@ def CodonAlphabet(gc=1, include_stop_codons=False):
     return a
 
 
-def _method_codon_alphabet(ignore, *args, **kwargs):
-    """If CodonAlphabet is set as a property, it gets self as extra 1st arg."""
-    return CodonAlphabet(*args, **kwargs)
-
-
 STANDARD_CODON = CodonAlphabet()
 
 # Modify NucleicAcidSequence to avoid circular import
-NucleicAcidSequence.codon_alphabet = _method_codon_alphabet
 NucleicAcidSequence.protein = PROTEIN
 ArrayRnaSequence.moltype = RNA
 ArrayRnaSequence.alphabet = RNA.alphabets.degen_gapped
diff --git a/src/cogent3/core/sequence.py b/src/cogent3/core/sequence.py
index 0c26d4490..d3baf5d01 100644
--- a/src/cogent3/core/sequence.py
+++ b/src/cogent3/core/sequence.py
@@ -1247,7 +1247,6 @@ class ProteinWithStopSequence(Sequence):
 class NucleicAcidSequence(Sequence):
     """Abstract base class for DNA and RNA sequences."""
 
-    PROTEIN = None  # will set in moltype
     codon_alphabet = None  # will set in moltype
 
     def reverse_complement(self):
@@ -1310,7 +1309,7 @@ class NucleicAcidSequence(Sequence):
 
         return self.__class__(codons, name=self.name, info=self.info)
 
-    def get_translation(self, gc=None, incomplete_ok=False):
+    def get_translation(self, gc=None, incomplete_ok=False, include_stop=False):
         """translate to amino acid sequence
 
         Parameters
@@ -1326,7 +1325,7 @@ class NucleicAcidSequence(Sequence):
         sequence of PROTEIN moltype
         """
         gc = get_code(gc)
-        codon_alphabet = self.codon_alphabet(gc).with_gap_motif()
+        codon_alphabet = gc.get_alphabet(include_stop=include_stop).with_gap_motif()
         # translate the codons
         translation = []
         for posn in range(0, len(self._seq) - 2, 3):
@@ -1499,9 +1498,9 @@ class SeqView:
 
             return self.__class__(self.seq, start=val, stop=val - 1, step=-1)
 
-    def _get_slice(self, slice, step):
-        slice_start = slice.start if slice.start is not None else 0
-        slice_stop = slice.stop if slice.stop is not None else len(self)
+    def _get_slice(self, segment, step):
+        slice_start = segment.start if segment.start is not None else 0
+        slice_stop = segment.stop if segment.stop is not None else len(self)
 
         if self.step > 0:
             return self._get_forward_slice_from_forward_seqview_(
@@ -1572,9 +1571,9 @@ class SeqView:
             step=self.step * step,
         )
 
-    def _get_reverse_slice(self, slice, step):
-        slice_start = slice.start if slice.start is not None else -1
-        slice_stop = slice.stop if slice.stop is not None else -len(self) - 1
+    def _get_reverse_slice(self, segment, step):
+        slice_start = segment.start if segment.start is not None else -1
+        slice_stop = segment.stop if segment.stop is not None else -len(self) - 1
 
         if self.step < 0:
             return self._get_reverse_slice_from_reverse_seqview_(
@@ -1661,18 +1660,18 @@ class SeqView:
             step=self.step * step,
         )
 
-    def __getitem__(self, slice):
-        if isinstance(slice, int):
-            return self._get_index(slice)
+    def __getitem__(self, segment):
+        if isinstance(segment, int):
+            return self._get_index(segment)
         if len(self) == 0:
             return self
 
-        slice_step = 1 if slice.step is None else slice.step
+        slice_step = 1 if segment.step is None else segment.step
 
         if slice_step > 0:
-            return self._get_slice(slice, slice_step)
+            return self._get_slice(segment, slice_step)
         elif slice_step < 0:
-            return self._get_reverse_slice(slice, slice_step)
+            return self._get_reverse_slice(segment, slice_step)
         else:
             raise ValueError(
                 f"{self.__class__.__name__} cannot be sliced with a step of 0"
diff --git a/src/cogent3/evolve/ns_substitution_model.py b/src/cogent3/evolve/ns_substitution_model.py
index d2089c323..c8f6c3712 100644
--- a/src/cogent3/evolve/ns_substitution_model.py
+++ b/src/cogent3/evolve/ns_substitution_model.py
@@ -1,6 +1,6 @@
 import numpy
 
-from cogent3.core import moltype
+from cogent3.core import genetic_code, moltype
 from cogent3.evolve.discrete_markov import PsubMatrixDefn
 from cogent3.evolve.predicate import MotifChange
 from cogent3.maths.optimisers import ParameterOutOfBoundsError
@@ -188,11 +188,11 @@ class NonReversibleTrinucleotide(NonReversibleNucleotide):
 class NonReversibleCodon(_Codon, NonReversibleNucleotide):
     """Base non-reversible codon substitution model."""
 
+    # todo deprecate alphabet argument
     @extend_docstring_from(Parametric.__init__)
     def __init__(self, alphabet=None, gc=None, **kw):
-        if gc is not None:
-            alphabet = moltype.CodonAlphabet(gc=gc)
-        kw["alphabet"] = alphabet or moltype.STANDARD_CODON
+        self.gc = genetic_code.get_code(gc)
+        kw["alphabet"] = self.gc.get_alphabet()
         NonReversibleNucleotide.__init__(self, **kw)
 
 
diff --git a/src/cogent3/evolve/substitution_model.py b/src/cogent3/evolve/substitution_model.py
index dc5af7d97..fbc02ca24 100644
--- a/src/cogent3/evolve/substitution_model.py
+++ b/src/cogent3/evolve/substitution_model.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 """
 substitution_model.py
 
@@ -40,7 +39,7 @@ import numpy
 
 from numpy.linalg import svd
 
-from cogent3.core import moltype
+from cogent3.core import genetic_code, moltype
 from cogent3.evolve import motif_prob_model, parameter_controller, predicate
 from cogent3.evolve.likelihood_tree import make_likelihood_tree_leaf
 from cogent3.evolve.substitution_calculation import (
@@ -1011,7 +1010,7 @@ class _Codon:
             return ndiffs == 1
 
     def get_predefined_predicates(self):
-        codon_preds = _CodonPredicates(self.get_alphabet().get_genetic_code())
+        codon_preds = _CodonPredicates(self.gc)
 
         preds = _TimeReversibleNucleotide.get_predefined_predicates(self)
         preds.update(
@@ -1028,9 +1027,9 @@ class _Codon:
 class TimeReversibleCodon(_Codon, _TimeReversibleNucleotide):
     """Core substitution model for codons"""
 
+    # todo deprecate alphabet argument
     @extend_docstring_from(_TimeReversibleNucleotide.__init__)
     def __init__(self, alphabet=None, gc=None, **kw):
-        if gc is not None:
-            alphabet = moltype.CodonAlphabet(gc=gc)
-        kw["alphabet"] = alphabet or moltype.STANDARD_CODON
+        self.gc = genetic_code.get_code(gc)
+        kw["alphabet"] = self.gc.get_alphabet()
         _TimeReversibleNucleotide.__init__(self, **kw)
diff --git a/src/cogent3/parse/sequence.py b/src/cogent3/parse/sequence.py
index 17779afe5..3b8d682f0 100644
--- a/src/cogent3/parse/sequence.py
+++ b/src/cogent3/parse/sequence.py
@@ -79,7 +79,7 @@ def FromFileParser(f, format, dialign_recode=False, **kw):
     f.close()
 
 
-def format_from_filename(filename, format=None):  # pragma: no coverage
+def format_from_filename(filename, format=None):  # pragma: no cover
     from cogent3.util.warning import deprecated
 
     deprecated(


Test patch:
diff --git a/tests/test_core/test_genetic_code.py b/tests/test_core/test_genetic_code.py
index dfc5371f6..fe578e094 100644
--- a/tests/test_core/test_genetic_code.py
+++ b/tests/test_core/test_genetic_code.py
@@ -3,6 +3,8 @@
 """
 from unittest import TestCase, main
 
+import pytest
+
 from cogent3 import DNA, RNA
 from cogent3.core.genetic_code import (
     DEFAULT,
@@ -414,6 +416,15 @@ class GeneticCodeTests(TestCase):
         self.assertIn("Standard", got)
 
 
-# Run tests if called from command line
-if __name__ == "__main__":
-    main()
+# starting pytest versions
+@pytest.mark.parametrize("code", (1, 2))
+def test_get_alphabet(code):
+    from cogent3.core.alphabet import Alphabet
+
+    gc = get_code()
+    alpha_w_stop = gc.get_alphabet(include_stop=True)
+    assert tuple(alpha_w_stop) == tuple(gc.codons)
+    assert isinstance(alpha_w_stop, Alphabet)
+    alpha_wo_stop = gc.get_alphabet(include_stop=False)
+    assert tuple(alpha_wo_stop) == tuple(gc.sense_codons)
+    assert isinstance(alpha_wo_stop, Alphabet)
diff --git a/tests/test_core/test_moltype.py b/tests/test_core/test_moltype.py
index 52f734ab7..ae3d70cda 100644
--- a/tests/test_core/test_moltype.py
+++ b/tests/test_core/test_moltype.py
@@ -8,7 +8,6 @@ from cogent3.core.moltype import (
     DNA,
     PROTEIN,
     RNA,
-    STANDARD_CODON,
     AlphabetError,
     AlphabetGroup,
     CodonAlphabet,
@@ -975,6 +974,8 @@ class DinucAlphabet(_AlphabetTestCase):
 
 class TestCodonAlphabet(_AlphabetTestCase):
     def setUp(self):
+        from cogent3.core.moltype import STANDARD_CODON
+
         self.alpha = STANDARD_CODON
 
     def test_ambiguous_gaps(self):