Repository: cogent3/cogent3
Pull request #2069 (2024-10-28 00:43:40)

ENH: check alphabet compatibility in SeqView coercion
[NEW] new_sequence._coerce_to_seqview provided a SeqView instance
    checks that the alphabet is compatible with the provided one
    by confirming the shared states have the same indexes.

## Summary by Sourcery

Enhance SeqView coercion to check for alphabet compatibility by verifying that shared state indexes are the same, and add tests to ensure correct behavior and error handling for incompatible alphabets.

Enhancements:
- Ensure SeqView coercion checks for alphabet compatibility by verifying shared state indexes.

Tests:
- Add tests for SeqView coercion to verify alphabet compatibility and handle incompatible alphabets.


Patch:
diff --git a/src/cogent3/core/new_sequence.py b/src/cogent3/core/new_sequence.py
index 8f3c29474..57b114de6 100644
--- a/src/cogent3/core/new_sequence.py
+++ b/src/cogent3/core/new_sequence.py
@@ -2648,6 +2648,17 @@ def _coerce_to_seqview(data, seqid, alphabet, offset) -> SeqViewABC:
 
 @_coerce_to_seqview.register
 def _(data: SeqViewABC, seqid, alphabet, offset) -> SeqViewABC:
+    # we require the indexes of shared states in alphabets to be the same
+    # SeqView has an alphabet but SeqViewABC does NOT because that is
+    # more general and covers the case where the SeqsData collection has the
+    # alphabet
+    if hasattr(data, "alphabet"):
+        n = min(len(data.alphabet), len(alphabet))
+        if data.alphabet[:n] != alphabet[:n]:
+            raise new_alphabet.AlphabetError(
+                f"element order {data.alphabet=} != to that in {alphabet=} for {data=!r}"
+            )
+
     if offset and data.offset:
         raise ValueError(
             f"cannot set {offset=} on a SeqView with an offset {data.offset=}"


Test patch:
diff --git a/tests/test_core/test_new_sequence.py b/tests/test_core/test_new_sequence.py
index f3a03ddb1..ca414a010 100644
--- a/tests/test_core/test_new_sequence.py
+++ b/tests/test_core/test_new_sequence.py
@@ -2595,3 +2595,33 @@ def test_load_invalid_moltype(aa_moltype):
     with pytest.raises(AssertionError):
         # this should be an AlphabetError
         cogent3.load_seq(aa_moltype, moltype="dna", new_type=True)
+
+
+@pytest.fixture(params=(new_moltype.DNA.alphabet, new_moltype.DNA.gapped_alphabet))
+def seqview(request):
+    return new_sequence.SeqView(seq="ACGT", alphabet=request.param, seqid="seq1")
+
+
+@pytest.mark.parametrize(
+    "alpha", (new_moltype.DNA.alphabet, new_moltype.DNA.degen_gapped_alphabet)
+)
+def test_make_seq_compatible_alpha(seqview, alpha):
+    got = new_sequence._coerce_to_seqview(seqview, seqview.seqid, alpha, 0)
+    assert got is seqview
+
+
+def test_make_seq_general_alpha_incompatible(seqview):
+    with pytest.raises(new_alphabet.AlphabetError):
+        alpha, seqview.alphabet = (
+            seqview.alphabet,
+            new_moltype.PROTEIN.degen_gapped_alphabet,
+        )
+        new_sequence._coerce_to_seqview(seqview, seqview.seqid, alpha, 0)
+
+
+def test_make_seq_wrong_order_alpha():
+    sv = new_sequence.SeqView(
+        seq="ACGT", alphabet=new_moltype.DNA.gapped_alphabet, seqid="seq1"
+    )
+    with pytest.raises(new_alphabet.AlphabetError):
+        new_sequence._coerce_to_seqview(sv, sv.seqid, new_moltype.DNA.degen_alphabet, 0)