Repository: cogent3/cogent3
Pull request #2195 (2025-01-16 04:16:59)

Misc fixes and style update
## Summary by Sourcery

Refactor assertion statements for improved readability.

Bug Fixes:
- Fixed a bug where sequence names provided in the offset were not found in the data.

Enhancements:
- Improved the representation of Aligned and Alignment objects.
- Simplified the logic for checking codon positions.
- Enhanced the error message for invalid moltypes.
- Improved error handling for invalid mask characters.
- Clarified the error message when likelihood functions are in the wrong order.
- Improved the error message for incompatible sequence classes in alignments.
- Improved the error message for mismatched tree tip names and alignment sequence names.

Tests:
- Added tests for applying sequence and feature names to slices.


Patch:
diff --git a/changelog.d/20250116_150556_Gavin.Huttley.md b/changelog.d/20250116_150556_Gavin.Huttley.md
new file mode 100644
index 000000000..1c2fbd8e5
--- /dev/null
+++ b/changelog.d/20250116_150556_Gavin.Huttley.md
@@ -0,0 +1,43 @@
+<!--
+A new scriv changelog fragment.
+
+Uncomment the section that is right (remove the HTML comment wrapper).
+-->
+
+<!--
+### Contributors
+
+- A bullet item for the Contributors category.
+
+-->
+
+### ENH
+
+- Slicing a new type sequence with a feature defaults to applying
+  the feature.name attribute to the resulting seq.name. This can be
+  controlled by the new apply_name argument to the
+  Feature.get_slice() method.
+
+### BUG
+
+- new_alphabet.KmerAlphabet.to_indices() consistently applies alphabet
+  dtype to the resulting array.
+
+<!--
+### DOC
+
+- A bullet item for the DOC category.
+
+-->
+<!--
+### Deprecations
+
+- A bullet item for the Deprecations category.
+
+-->
+<!--
+### Discontinued
+
+- A bullet item for the Discontinued category.
+
+-->
diff --git a/src/cogent3/core/annotation.py b/src/cogent3/core/annotation.py
index 800d1dc6b..e88c532c6 100644
--- a/src/cogent3/core/annotation.py
+++ b/src/cogent3/core/annotation.py
@@ -74,7 +74,12 @@ class Feature:
     def name(self):
         return self._name
 
-    def get_slice(self, complete: bool = False, allow_gaps: bool = False):
+    def get_slice(
+        self,
+        complete: bool = False,
+        allow_gaps: bool = False,
+        apply_name: bool = True,
+    ):
         """
         The corresponding sequence fragment.
 
@@ -85,6 +90,8 @@ class Feature:
             raised. If False, gaps are removed.
         allow_gaps
             if on an alignment, includes the gap positions
+        apply_name
+            assigns self.name to the resulting seq.name
 
         Returns
         -------
@@ -101,17 +108,19 @@ class Feature:
             fmap = fmap.without_gaps()
         if not allow_gaps:
             result = self.parent[fmap]
-            return self._do_seq_slice(result)
+            return self._do_seq_slice(result, apply_name)
         # all slicing now requires start < end
         result = self.parent[fmap.start : fmap.end]
-        return self._do_seq_slice(result)
+        return self._do_seq_slice(result, apply_name)
 
-    def _do_seq_slice(self, result):
+    def _do_seq_slice(self, result, apply_name):
         if self.reversed:
             result = result.rc()
         if self.map.num_spans > 1:
             # db querying will be incorrect so make sure it can't be done
             result.annotation_db = None
+        if apply_name:
+            result.name = self.name
         return result
 
     def without_lost_spans(self):
diff --git a/src/cogent3/core/new_alphabet.py b/src/cogent3/core/new_alphabet.py
index 97e8af34c..a63349a0e 100644
--- a/src/cogent3/core/new_alphabet.py
+++ b/src/cogent3/core/new_alphabet.py
@@ -1231,7 +1231,7 @@ class SenseCodonAlphabet(tuple, AlphabetABC, KmerAlphabetABC):
     @functools.singledispatchmethod
     def to_indices(self, seq) -> numpy.ndarray:
         """returns a sequence of codon indices"""
-        msg = f"{type(seq)} is not supported"
+        msg = f"{type(seq)} is invalid"
         raise TypeError(msg)
 
     @to_indices.register


Test patch:
diff --git a/tests/test_core/test_new_alphabet.py b/tests/test_core/test_new_alphabet.py
index 1ec89acd2..241c1cbe2 100644
--- a/tests/test_core/test_new_alphabet.py
+++ b/tests/test_core/test_new_alphabet.py
@@ -786,3 +786,21 @@ def test_trinucs_to_indices_dtype(alpha):
     assert len(small) == len(small.tobytes())
     full = alpha.to_indices(s)
     assert len(full) == len(full.tobytes())
+
+
+@pytest.mark.parametrize(
+    "alpha",
+    [
+        new_moltype.DNA.alphabet,
+        new_moltype.DNA.alphabet.get_kmer_alphabet(k=2),
+        new_moltype.DNA.alphabet.get_kmer_alphabet(k=3),
+        new_moltype.DNA.alphabet.get_kmer_alphabet(k=4),
+        new_genetic_code.DEFAULT.get_alphabet(include_stop=False),
+    ],
+)
+@pytest.mark.parametrize("cast", [numpy.array, str])
+def test_consistent_dtype(alpha, cast):
+    seq = new_moltype.DNA.make_seq(seq="ATG" * 600, name="s1")
+    seq = cast(seq)
+    indices = alpha.to_indices(seq)
+    assert indices.dtype == alpha.dtype
diff --git a/tests/test_core/test_new_seq_annotation.py b/tests/test_core/test_new_seq_annotation.py
index b03b85798..105b9b08c 100644
--- a/tests/test_core/test_new_seq_annotation.py
+++ b/tests/test_core/test_new_seq_annotation.py
@@ -162,3 +162,21 @@ def test_gbdb_get_children_get_parent(DATA_DIR):
     (child,) = list(orig.get_children("CDS"))
     parent, *_ = list(child.get_parent())
     assert parent == orig
+
+
+def test_feature_names_seq():
+    raw_seq = "AACCCAAAATTTTTTGGGGGGGGGGCCCC"
+    cds = (15, 25)
+    seq = DNA.make_seq(seq=raw_seq, name="s1")
+    f = seq.add_feature(biotype="CDS", name="s1-cds", spans=[cds])
+    assert f.name == "s1-cds"
+    # apply the sequence name to the feature
+    s = f.get_slice(apply_name=False)
+    assert s.name == seq.name
+    # or apply the feature name
+    s = f.get_slice(apply_name=True)
+    assert s.name == "s1-cds" != seq.name
+    # default behaviour is to apply the feature name
+    s = seq[f]
+    assert s.name == "s1-cds"
+    assert s.name == f.name