nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: cogent3/cogent3
Pull request #1460 (2023-05-29 03:48:26)

The sqlite annotation db classes support being written to disk


Patch:
diff --git a/src/cogent3/core/annotation_db.py b/src/cogent3/core/annotation_db.py
index 1ab52f4ad..68ff3ac4d 100644
--- a/src/cogent3/core/annotation_db.py
+++ b/src/cogent3/core/annotation_db.py
@@ -155,13 +155,12 @@ class SupportsWriteFeatures(typing.Protocol):  # should be defined centrally
 
 @typing.runtime_checkable
 class SupportsFeatures(
-    SupportsQueryFeatures, SupportsWriteFeatures, SerialisableType, typing.Protocol
+    SupportsQueryFeatures,
+    SupportsWriteFeatures,
+    SerialisableType,
+    typing.Sized,
+    typing.Protocol,
 ):
-    @property
-    def db(self):
-        # pointer to the actual db
-        ...
-
     def __len__(self):
         # the number of records
         ...
@@ -986,6 +985,13 @@ class SqliteAnnotationDbMixin:
     def to_json(self) -> str:
         return json.dumps(self.to_rich_dict())
 
+    def write(self, path: os.PathLike) -> None:
+        """writes db as bytes to path"""
+        backup = sqlite3.connect(path)
+        with self.db:
+            self.db.backup(backup)
+        backup.close()
+
 
 class BasicAnnotationDb(SqliteAnnotationDbMixin):
     """Provides a user table for annotations. This can be merged with
@@ -1402,6 +1408,6 @@ def load_annotations(
     path = pathlib.Path(path)
     return (
         _db_from_genbank(path, db=db)
-        if ".gb" in path.suffixes
+        if {".gb", ".gbk"} & set(path.suffixes)
         else _db_from_gff(path, seqids=seqids, db=db)
     )


Test patch:
diff --git a/tests/test_core/test_annotation_db.py b/tests/test_core/test_annotation_db.py
index b63cf9a0f..400a1d42d 100644
--- a/tests/test_core/test_annotation_db.py
+++ b/tests/test_core/test_annotation_db.py
@@ -776,3 +776,11 @@ def test_gb_namer():
     db = GenbankAnnotationDb(data=data, namer=_custom_namer, seqid=got[0]["locus"])
     # there are 2 repeat regions, which we don't catch with our namer
     assert db.num_matches(name="default name") == 2
+
+
+def test_write(gb_db, tmp_path):
+    outpath = tmp_path / "ondisk.gbkdb"
+    gb_db.write(outpath)
+    got = GenbankAnnotationDb(source=outpath)
+    assert got.to_rich_dict()["tables"] == gb_db.to_rich_dict()["tables"]
+    assert isinstance(got, GenbankAnnotationDb)
Repository: cogent3/cogent3
Pull request #1486 (2023-06-25 23:55:20)

MAINT: tidy in SeqKmers


Patch:
diff --git a/src/cogent3/align/pycompare.py b/src/cogent3/align/pycompare.py
index faa7dd734..ac68fe348 100644
--- a/src/cogent3/align/pycompare.py
+++ b/src/cogent3/align/pycompare.py
@@ -5,8 +5,6 @@ from dataclasses import InitVar, dataclass, field
 from itertools import product
 from typing import Generator, Optional
 
-import cogent3.util.progress_display as UI
-
 from cogent3.core.sequence import Sequence
 
 
@@ -165,8 +163,6 @@ def _extend_from_position(
         positions to evaluate
     threshold : int
         minimum number of matches
-    total : int
-        total number of True values in matches
 
     Returns
     -------
@@ -280,6 +276,7 @@ class SeqKmers:
 
         self.kmers = kmers
         self.num_seqs = 1
+        self.other_name = None
 
     def add_seq(self, seq: Sequence) -> None:
         """transforms seq into k-mers, adds indices of matches
@@ -304,10 +301,14 @@ class SeqKmers:
 
         self.num_seqs += 1
 
-    def drop_seq(self, seq_name: str) -> None:
+    def drop_seq(self, seq_name: Optional[str] = None) -> None:
         """removes other seq from all k-mers"""
+        seq_name = seq_name if seq_name else self.other_name
+        if seq_name is None:
+            return
+
         for kmer in self.kmers:
-            kmer.indices.pop(self.other_name, None)
+            kmer.indices.pop(seq_name, None)
 
         self.other_name = None
         self.num_seqs = 1


Test patch:
diff --git a/tests/test_align/test_compare.py b/tests/test_align/test_compare.py
index 3ab482f1a..9e2213fb8 100644
--- a/tests/test_align/test_compare.py
+++ b/tests/test_align/test_compare.py
@@ -253,6 +253,25 @@ def test_seqkmers_2seqs_dropseq(smallseq):
     assert sk.num_seqs == 1
 
 
+def test_dropseq_default(smallseq):
+    seq1 = make_seq(smallseq, name="seq1", moltype="dna")
+    seq2 = make_seq(smallseq[1:], name="seq2", moltype="dna")
+    sk = SeqKmers(seq1, 2, canonical=set(seq1.moltype))
+    sk.add_seq(seq2)
+    sk.drop_seq()
+    assert sk.other_name is None
+
+
+def test_one_seq_dropseq(smallseq):
+    seq1 = make_seq(smallseq, name="seq1", moltype="dna")
+    sk = SeqKmers(seq1, 2, canonical=set(seq1.moltype))
+    assert sk.other_name is None
+    assert sk.num_seqs == 1
+    sk.drop_seq()
+    assert sk.other_name is None
+    assert sk.num_seqs == 1
+
+
 def test_seqkmers_iter_matching_kmers(smallseq):
     seq1 = make_seq(smallseq, name="seq1", moltype="dna")
     seq2 = make_seq(smallseq[1:], name="seq2", moltype="dna")
Repository: cogent3/cogent3
Pull request #1489 (2023-06-27 04:10:45)

MAINT: better NotCompleted result from sp_score
[CHANGED] The alignment quality sp_score relies on computing pairwise
     genetic distanxces. This can fail due to NaN's in those distances.
     Return a more informative NotCompleted object.


Patch:
diff --git a/src/cogent3/app/align.py b/src/cogent3/app/align.py
index 3f4069d63..4e810db2b 100644
--- a/src/cogent3/app/align.py
+++ b/src/cogent3/app/align.py
@@ -4,9 +4,8 @@ from bisect import bisect_left
 from itertools import combinations
 from typing import Optional, Union
 
-from numpy import array
+from numpy import array, isnan
 
-from cogent3 import make_tree
 from cogent3.align import (
     global_pairwise,
     make_dna_scoring_dict,
@@ -713,6 +712,18 @@ class sp_score:
 
         self._calc(aln, show_progress=False)
         dmat = self._calc.dists
+
+        if isnan(dmat.array).sum():
+            nans = isnan(dmat.array).sum(axis=0) > 0
+            names = ", ".join(f"{n!r}" for n in array(dmat.names)[nans])
+            return NotCompleted(
+                "ERROR",
+                self,
+                f"Some genetic distances involving {names} were NaN's. "
+                "Using calc='pdist' will prevent this issue.",
+                source=aln,
+            )
+
         lengths = self._calc.lengths
         for i, j in combinations(range(aln.num_seqs), 2):
             n1, n2 = dmat.names[i], dmat.names[j]


Test patch:
diff --git a/tests/test_app/test_align.py b/tests/test_app/test_align.py
index 4968b2978..6168048e9 100644
--- a/tests/test_app/test_align.py
+++ b/tests/test_app/test_align.py
@@ -562,6 +562,16 @@ def test_sp_score_exclude_gap():
     assert_allclose(got, expect)
 
 
+def test_sp_fail():
+    aln = make_aligned_seqs(
+        data={"a": "ATG---------AATCGAAGA", "b": "GTG---------GAAAAGCAG"}, moltype="dna"
+    )
+    app = get_app("sp_score")
+    got = app.main(aln)
+    assert isinstance(got, NotCompleted)
+    assert "NaN" in got.message
+
+
 def test_sp_score_additive_gap():
     # additive gap score
     app = get_app("sp_score", calc="pdist", gap_extend=1, gap_insert=0)
Repository: cogent3/cogent3
Pull request #1530 (2023-07-11 06:29:03)

Add test for querying for non-existent parent/child


Patch:
diff --git a/src/cogent3/core/annotation_db.py b/src/cogent3/core/annotation_db.py
index 2d21319fa..adecfeb1b 100644
--- a/src/cogent3/core/annotation_db.py
+++ b/src/cogent3/core/annotation_db.py
@@ -691,6 +691,9 @@ class SqliteAnnotationDbMixin:
                 # multiple values for parent means this is better expressed
                 # as an OR clause
                 # todo modify the conditional SQL generation
+                if not result["parent_id"]:
+                    return
+
                 for name in result["parent_id"].replace(" ", "").split(","):
                     if parent := list(
                         self._get_feature_by_id(


Test patch:
diff --git a/tests/data/simple2.gff b/tests/data/simple2.gff
index 641aa020d..65aff505a 100644
--- a/tests/data/simple2.gff
+++ b/tests/data/simple2.gff
@@ -4,3 +4,6 @@ test_seq	.	CDS 	3	9	.	+	.	ID=cds3
 test_seq	.	exon	12	20	.	+	.	ID=exon4;parent=cds1
 test_seq	.	CDS	    4	10	.	+	.	ID=cds4
 test_seq	.	CpG 	3	12	.	+	.	ID=CpG2
+test_seq	.	mRNA	10	70	.	-	.	ID=Transcript:childless;Note=amx-2;Parent=parentless
+test_seq	.	gene	1	100	.	-	.	ID=parentless
+
diff --git a/tests/test_core/test_annotation_db.py b/tests/test_core/test_annotation_db.py
index e772e04b5..08841833f 100644
--- a/tests/test_core/test_annotation_db.py
+++ b/tests/test_core/test_annotation_db.py
@@ -204,6 +204,34 @@ def test_gff_get_parent(gff_db, name, expected):
     assert {g["name"] for g in got} == set(expected)
 
 
+def test_gff_get_children_empty():
+    """if feature has no children then should return []"""
+    db = load_annotations(path=DATA_DIR / "simple2.gff")
+    got = list(db.get_feature_children(name="childless"))
+    assert got == []
+
+
+def test_gff_get_parent_empty():
+    """if feature has no parent then should return []"""
+    db = load_annotations(path=DATA_DIR / "simple2.gff")
+    got = list(db.get_feature_parent(name="parentless"))
+    assert got == []
+
+
+def test_gff_get_children_non_existent():
+    """if feature does not exist then should return []"""
+    db = load_annotations(path=DATA_DIR / "simple2.gff")
+    got = list(db.get_feature_children(name="nonexistendID"))
+    assert got == []
+
+
+def test_gff_get_parent_non_existent():
+    """if feature does not exist then should return []"""
+    db = load_annotations(path=DATA_DIR / "simple2.gff")
+    got = list(db.get_feature_parent(name="nonexistendID"))
+    assert got == []
+
+
 def test_gff_counts(gff_db):
     got = gff_db.biotype_counts()
     assert len(got) > 0
Repository: cogent3/cogent3
Pull request #1618 (2023-11-13 22:59:16)

BUG: load_annotations() now raises IOError if no matching paths found


Patch:
diff --git a/src/cogent3/core/annotation_db.py b/src/cogent3/core/annotation_db.py
index b5b59eee8..5ac5a59d0 100644
--- a/src/cogent3/core/annotation_db.py
+++ b/src/cogent3/core/annotation_db.py
@@ -1456,6 +1456,7 @@ def _db_from_genbank(path: os.PathLike, db: SupportsFeatures, write_path, **kwar
     paths = list(paths.parent.glob(paths.name))
 
     ui = kwargs.pop("ui")
+    one_valid_path = False
     for path in ui.series(paths):
         with open_(path) as infile:
             rec = list(MinimalGenbankParser(infile))[0]
@@ -1465,7 +1466,10 @@ def _db_from_genbank(path: os.PathLike, db: SupportsFeatures, write_path, **kwar
                 seqid=rec["locus"],
                 db=db,
             )
+            one_valid_path = True
 
+    if not one_valid_path:
+        raise IOError(f"{str(path)!r} not found")
     return db
 
 
@@ -1490,6 +1494,7 @@ def _db_from_gff(
     paths = list(paths.parent.glob(paths.name))
 
     ui = kwargs.pop("ui")
+    one_valid_path = False
     for path in ui.series(paths):
         data = list(
             gff_parser(
@@ -1499,6 +1504,9 @@ def _db_from_gff(
             )
         )
         db = GffAnnotationDb(source=write_path, data=data, db=db)
+        one_valid_path = True
+    if not one_valid_path:
+        raise IOError(f"{str(path)!r} not found")
     return db
 
 


Test patch:
diff --git a/tests/test_core/test_annotation_db.py b/tests/test_core/test_annotation_db.py
index 9a87d9831..7b10eab02 100644
--- a/tests/test_core/test_annotation_db.py
+++ b/tests/test_core/test_annotation_db.py
@@ -945,3 +945,8 @@ def test_gbdb_get_children_fails_no_coords(gb_db):
 def test_gbdb_get_parent_fails_no_coords(gb_db):
     with pytest.raises(ValueError):
         _ = list(gb_db.get_feature_parent(name="CNA00110"))
+
+
+def test_load_annotations_invalid_path():
+    with pytest.raises(IOError):
+        db = load_annotations(path="invalidfile.gff3")
Repository: cogent3/cogent3
Pull request #1637 (2023-11-21 18:22:56)

Filter available_apps by name
To resolve https://github.com/cogent3/cogent3/issues/1633

- [x] Confirm that test case passes


Patch:
diff --git a/src/cogent3/app/__init__.py b/src/cogent3/app/__init__.py
index c9724f775..31068dc94 100644
--- a/src/cogent3/app/__init__.py
+++ b/src/cogent3/app/__init__.py
@@ -50,7 +50,7 @@ def _make_types(app) -> dict:
     return _types
 
 
-def available_apps():
+def available_apps(app_name_filter: str | None = None):
     """returns Table listing the available apps"""
     from cogent3.util.table import Table
 
@@ -68,6 +68,9 @@ def available_apps():
         if any(app.startswith(d) for d in deprecated):
             continue
 
+        if app_name_filter and app_name_filter not in app:
+            continue
+
         with contextlib.suppress(AttributeError):
             # probably a local scope issue in testing!
             rows.append(_get_app_attr(app, is_comp))


Test patch:
diff --git a/tests/test_app/test_init.py b/tests/test_app/test_init.py
index 63ada9187..a548b1533 100644
--- a/tests/test_app/test_init.py
+++ b/tests/test_app/test_init.py
@@ -210,5 +210,14 @@ def test_app_help_signature(capsys, app_name):
         assert got.count("\n") > 1
 
 
+def test_available_apps_filter():
+    """available apps can be filtered by name"""
+    app_name_filter: str = "load"
+    filtered_apps = available_apps(app_name_filter)
+    assert isinstance(filtered_apps, Table)
+    assert len(filtered_apps) > 0
+    # check every returned table row 'name' has filter in it
+    assert sum(app_name_filter in n for n in filtered_apps.columns["name"]) == len(filtered_apps)
+
 if __name__ == "__main__":
     main()
Repository: cogent3/cogent3
Pull request #1669 (2023-12-12 05:29:20)

DEP: Add Table.to_pandas() #1658
Also updated `.gitignore` to ignore the wing IDE project file and `doc/_build`


Patch:
diff --git a/.gitignore b/.gitignore
index 0b60b315b..3bf432410 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,7 +17,7 @@ develop-eggs
 .installed.cfg
 lib
 lib64
-doc/draw
+doc/_build
 
 # Installer logs
 pip-log.txt
@@ -43,6 +43,7 @@ lcov*.info
 .DS_Store
 __pycache__
 *.code-workspace
+*.wpr
 *.wpu
 .vscode/*
 venv/
diff --git a/doc/cookbook/tables.rst b/doc/cookbook/tables.rst
index ed4196adb..9e9d82f43 100644
--- a/doc/cookbook/tables.rst
+++ b/doc/cookbook/tables.rst
@@ -342,14 +342,14 @@ Get the table as a ``pandas.DataFrame``
 .. jupyter-execute::
 
     table = load_table("data/stats.tsv")
-    df = table.to_dataframe()
+    df = table.to_pandas()
     df
 
 You can also specify column(s) are categories
 
 .. jupyter-execute::
 
-    df = table.to_dataframe(categories="Region")
+    df = table.to_pandas(categories="Region")
 
 Get a table of counts as a contingency table
 ============================================
diff --git a/src/cogent3/util/table.py b/src/cogent3/util/table.py
index 0cd06d47b..efbcbca13 100644
--- a/src/cogent3/util/table.py
+++ b/src/cogent3/util/table.py
@@ -1964,7 +1964,7 @@ class Table:
         data = self.to_rich_dict()
         return json.dumps(data)
 
-    def to_dataframe(self, categories=None):
+    def to_pandas(self, categories=None):
         """returns pandas DataFrame instance
 
         Parameters
@@ -1987,6 +1987,14 @@ class Table:
 
         return df
 
+    @c3warn.deprecated_callable(
+        version="2024.3", reason="to enable usage by plotly", new="to_pandas()"
+    )
+    def to_dataframe(self, **kwargs):  # pragma: no cover
+        """deprecated, use to_pandas()"""
+
+        return self.to_pandas(**kwargs)
+
     def to_plotly(self, width=500, font_size=12, layout=None, **kwargs):
         """returns a Plotly Table"""
         from cogent3.draw.drawable import Drawable


Test patch:
diff --git a/tests/test_util/test_table.py b/tests/test_util/test_table.py
index cb9fd455e..d2e25c936 100644
--- a/tests/test_util/test_table.py
+++ b/tests/test_util/test_table.py
@@ -1742,10 +1742,10 @@ class TableTests(TestCase):
         table.display = display
 
     @skipIf(DataFrame is None, "pandas not installed")
-    def test_to_dataframe(self):
+    def test_to_pandas(self):
         """produces a dataframe"""
         t = Table(header=self.t1_header, data=self.t1_rows)
-        df = t.to_dataframe()
+        df = t.to_pandas()
         self.assertIsInstance(df, DataFrame)
         data = df.to_numpy()
         self.assertEqual(data.tolist(), self.t1_rows)
Repository: cogent3/cogent3
Pull request #1686 (2024-01-01 23:09:40)

MAINT: improved handling of sequence creation via MolType


Patch:
diff --git a/src/cogent3/core/alignment.py b/src/cogent3/core/alignment.py
index ae3337312..4a5e6e298 100644
--- a/src/cogent3/core/alignment.py
+++ b/src/cogent3/core/alignment.py
@@ -1727,6 +1727,7 @@ class _SequenceCollectionBase:
             new[new_name] = new_seq
         result = self.__class__(data=new, info=self.info, moltype=self.moltype)
         result.info.name_map = name_map
+        result.annotation_db = DEFAULT_ANNOTATION_DB()
         return result
 
     @UI.display_wrap
@@ -5647,6 +5648,11 @@ def _(data: Aligned, moltype) -> Sequence:
     return data.get_gapped_seq().to_moltype(moltype)
 
 
+@_construct_unaligned_seq.register
+def _(data: ArraySequence, moltype) -> Sequence:
+    return moltype.make_seq(str(data), name=data.name, info=data.info)
+
+
 @_construct_unaligned_seq.register
 def _(data: ndarray, moltype) -> Sequence:
     return moltype.make_array_seq(data)
diff --git a/src/cogent3/core/moltype.py b/src/cogent3/core/moltype.py
index 9ebc02a08..9cfcf5f45 100644
--- a/src/cogent3/core/moltype.py
+++ b/src/cogent3/core/moltype.py
@@ -734,7 +734,28 @@ class MolType(object):
         return self.label
 
     def make_seq(self, seq, name=None, **kwargs):
-        """Returns sequence of correct type."""
+        """Returns sequence generated by seq_constructor argument to self..
+
+        Parameters
+        ----------
+        seq
+            valid input to the coerce_str function
+        name
+            value assigned to sequence.name attribute
+        kwargs
+            optional keyword arguments to bound sequence constructor class
+
+        Notes
+        -----
+        If input seq has a moltype attribute that is self, seq is
+        returned unmodified. If the moltype is not self, then the
+        sequence is converted using seq.to_moltype(self) .
+        """
+        if getattr(seq, "moltype", None) is self and (name is None or seq.name == name):
+            return seq
+
+        if hasattr(seq, "to_moltype") and (name is None or seq.name == name):
+            return seq.to_moltype(self)
 
         name = name or getattr(seq, "name", None)
         if isinstance(seq, ArraySequence):


Test patch:
diff --git a/tests/test_core/test_moltype.py b/tests/test_core/test_moltype.py
index 382df337e..7e59054c8 100644
--- a/tests/test_core/test_moltype.py
+++ b/tests/test_core/test_moltype.py
@@ -1139,3 +1139,16 @@ def test_resolve_ambiguity_codons():
         DNA.resolve_ambiguity("at-")
     with pytest.raises(AlphabetError):
         DNA.resolve_ambiguity("---", alphabet=codon_alpha)
+
+
+def test_make_seq_on_seq():
+    seq = DNA.make_seq("ACGG")
+    got = DNA.make_seq(seq)
+    assert got is seq
+
+
+def test_make_seq_diff_moltype():
+    seq = RNA.make_seq("ACGG")
+    seq.add_feature(biotype="gene", name="test", spans=[(0, 2)])
+    got = DNA.make_seq(seq)
+    assert len(got.annotation_db) == 1
Repository: cogent3/cogent3
Pull request #1699 (2024-01-24 03:57:34)

BUG: fix ArrayAlignment.get_degapped_relative_to


Patch:
diff --git a/src/cogent3/core/alignment.py b/src/cogent3/core/alignment.py
index 39ee814aa..319e9ff89 100644
--- a/src/cogent3/core/alignment.py
+++ b/src/cogent3/core/alignment.py
@@ -4192,7 +4192,7 @@ class ArrayAlignment(AlignmentI, _SequenceCollectionBase):
         new = self.array_seqs[:, indices]
 
         return self.__class__(
-            new.T, names=self.names, moltype=self.moltype, info=self.info
+            new, names=self.names, moltype=self.moltype, info=self.info
         )
 
     def add_from_ref_aln(self, ref_aln, before_name=None, after_name=None):


Test patch:
diff --git a/tests/test_core/test_alignment.py b/tests/test_core/test_alignment.py
index fcf622061..f5439cebd 100644
--- a/tests/test_core/test_alignment.py
+++ b/tests/test_core/test_alignment.py
@@ -2008,28 +2008,6 @@ class AlignmentTests(AlignmentBaseTests, TestCase):
         self.assertEqual(r1, {"seq1": "T-G", "seq2": "TCG"})
         self.assertEqual(r2, {"seq1": "--G", "seq2": "TCG"})
 
-    def test_get_degapped_relative_to(self):
-        """should remove all columns with a gap in sequence with given name"""
-        aln = self.Class(
-            [
-                ["name1", "-AC-DEFGHI---"],
-                ["name2", "XXXXXX--XXXXX"],
-                ["name3", "YYYY-YYYYYYYY"],
-                ["name4", "-KL---MNPR---"],
-            ]
-        )
-        out_aln = self.Class(
-            [
-                ["name1", "ACDEFGHI"],
-                ["name2", "XXXX--XX"],
-                ["name3", "YY-YYYYY"],
-                ["name4", "KL--MNPR"],
-            ]
-        )
-        self.assertEqual(aln.get_degapped_relative_to("name1"), out_aln)
-
-        self.assertRaises(ValueError, aln.get_degapped_relative_to, "nameX")
-
     def test_get_degapped_relative_to_info(self):
         """should remove all columns with a gap in sequence with given name
         while preserving info attribute"""
@@ -3527,3 +3505,29 @@ def test_make_case():
     data = {"a": "tata", "b": "tgtc", "c": "gcga", "d": "gaac", "e": "gagc"}
     got = make_aligned_seqs(data=data, moltype="dna")
     assert got == {k: v.upper() for k, v in data.items()}
+
+
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment))
+def test_get_degapped_relative_to(cls):
+    """should remove all columns with a gap in sequence with given name"""
+    aln = cls(
+        [
+            ["name1", "-AC-DEFGHI---"],
+            ["name2", "XXXXXX--XXXXX"],
+            ["name3", "YYYY-YYYYYYYY"],
+            ["name4", "-KL---MNPR---"],
+        ]
+    )
+    expect = dict(
+        [
+            ["name1", "ACDEFGHI"],
+            ["name2", "XXXX--XX"],
+            ["name3", "YY-YYYYY"],
+            ["name4", "KL--MNPR"],
+        ]
+    )
+    result = aln.get_degapped_relative_to("name1")
+    assert result.to_dict() == expect
+
+    with pytest.raises(ValueError):
+        aln.get_degapped_relative_to("nameX")
Repository: cogent3/cogent3
Pull request #1716 (2024-02-13 05:46:44)

FIX: empty SeqView absolute/relative position returns 0


Patch:
diff --git a/src/cogent3/core/sequence.py b/src/cogent3/core/sequence.py
index 390fc62e8..59b856156 100644
--- a/src/cogent3/core/sequence.py
+++ b/src/cogent3/core/sequence.py
@@ -2007,6 +2007,8 @@ class SeqView:
         the absolute index with respect to the coordinates of the self
         including offset
         """
+        if not self:
+            return 0
 
         if rel_index < 0:
             raise IndexError("only positive indexing supported!")
@@ -2029,6 +2031,8 @@ class SeqView:
         NOTE: the returned value DOES NOT reflect python indexing. Importantly, negative values represent positions that
         precede the current view.
         """
+        if not self:
+            return 0
 
         if abs_index < 0:
             raise IndexError("Index must be +ve and relative to the + strand")


Test patch:
diff --git a/tests/test_core/test_sequence.py b/tests/test_core/test_sequence.py
index 52664911e..10bb249f1 100644
--- a/tests/test_core/test_sequence.py
+++ b/tests/test_core/test_sequence.py
@@ -2714,3 +2714,9 @@ def test_sequences_propogates_seqid():
 def test_make_seq_assigns_to_seqview():
     seq = cogent3.make_seq("ACGT", name="s1")
     assert seq.name == seq._seq.seqid == "s1"
+
+
+def test_empty_seqview_translate_position():
+    sv = SeqView("")
+    assert sv.absolute_position(0) == 0
+    assert sv.relative_position(0) == 0