nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: cogent3/cogent3
Pull request #2047 (2024-10-16 02:58:25)

ENH: simplify display from available_apps()
[CHANGED] Renamed the column "module" as "package". Now only lists the package,
    not the fully resolved path to the app.

## Summary by Sourcery

Simplify the display of available apps by renaming the 'module' column to 'package' and listing only the package name. Add a test to ensure the correct package name is displayed.

Enhancements:
- Simplify the display of available apps by renaming the column 'module' to 'package' and listing only the package name instead of the full path.

Tests:
- Add a test to verify that available apps list only the package name.


Patch:
diff --git a/doc/app/app_overview/available-apps.rst b/doc/app/app_overview/available-apps.rst
index c61bcc76b..1271d308a 100644
--- a/doc/app/app_overview/available-apps.rst
+++ b/doc/app/app_overview/available-apps.rst
@@ -16,3 +16,6 @@ The ``name_filter`` argument can be used to display only the apps that match a s
 .. jupyter-execute::
 
     available_apps(name_filter="load")
+
+
+.. note:: The first column lists the package providing the app.
\ No newline at end of file
diff --git a/src/cogent3/app/__init__.py b/src/cogent3/app/__init__.py
index c8ff44175..4c08a219d 100644
--- a/src/cogent3/app/__init__.py
+++ b/src/cogent3/app/__init__.py
@@ -34,9 +34,8 @@ def _get_extension_attr(extension):
         )
 
     _types = _make_types(obj)
-
     return [
-        extension.module_name,
+        extension.module_name.split(".")[0],
         extension.name,
         is_app_composable(obj),
         _doc_summary(obj.__doc__ or ""),
@@ -90,7 +89,7 @@ def available_apps(name_filter: str | None = None) -> Table:
             # probably a local scope issue in testing!
             rows.append(_get_extension_attr(extension))
 
-    header = ["module", "name", "composable", "doc", "input type", "output type"]
+    header = ["package", "name", "composable", "doc", "input type", "output type"]
     return Table(header=header, data=rows)
 
 


Test patch:
diff --git a/tests/test_app/test_init.py b/tests/test_app/test_init.py
index 9b3738cc9..ebc5b2d93 100644
--- a/tests/test_app/test_init.py
+++ b/tests/test_app/test_init.py
@@ -165,3 +165,11 @@ def test_available_apps_filter():
     assert sum(app_name_filter in n for n in filtered_apps.columns["name"]) == len(
         filtered_apps
     )
+
+
+def test_available_apps_package():
+    """available apps lists just the package"""
+    app_name_filter: str = "model"
+    filtered_apps = available_apps(app_name_filter)
+    assert filtered_apps.columns["package"][0] == "cogent3"
+    assert all("." not in pkg for pkg in filtered_apps.columns["package"])
Repository: cogent3/cogent3
Pull request #2059 (2024-10-23 10:40:47)

ENH: handled comparison between nodes where name is None
## Summary by Sourcery

Enhance TreeNode comparison methods to handle cases where the name attribute is None by treating it as an empty string, and add corresponding test cases to ensure correct behavior.

Enhancements:
- Handle comparison between TreeNode objects where the name attribute is None by treating None as an empty string.

Tests:
- Add test cases to verify TreeNode comparison behavior when the name attribute is None.


Patch:
diff --git a/src/cogent3/core/tree.py b/src/cogent3/core/tree.py
index 3bdbf6c34..c3c88fb90 100644
--- a/src/cogent3/core/tree.py
+++ b/src/cogent3/core/tree.py
@@ -128,10 +128,16 @@ class TreeNode:
     # return self.name != other.name
 
     def __lt__(self, other):
-        return self.name < other.name
+        self_name = self.name or ""
+        other_name = other.name or ""
+
+        return self_name < other_name
 
     def __gt__(self, other):
-        return self.name > other.name
+        self_name = self.name or ""
+        other_name = other.name or ""
+
+        return self_name > other_name
 
     def compare_name(self, other):
         """Compares TreeNode by name"""


Test patch:
diff --git a/tests/test_core/test_tree.py b/tests/test_core/test_tree.py
index 28a2c0f63..d054bc795 100644
--- a/tests/test_core/test_tree.py
+++ b/tests/test_core/test_tree.py
@@ -1229,6 +1229,14 @@ class TreeNodeTests(TestCase):
         result = self.t.compare_by_subsets(self.TreeRoot)
         self.assertEqual(result, 1)
 
+    def test_treenode_comparison_with_none_name(self):
+        assert self.Empty < self.Single
+        assert self.Single > self.Empty
+        assert self.Single > TreeNode(name=None)
+        assert TreeNode(name=None) < self.Single
+        assert TreeNode(name="test") > self.Empty
+        assert self.Empty < TreeNode(name="test")
+
 
 class PhyloNodeTests(TestCase):
     """Tests of phylogeny-specific methods."""
Repository: cogent3/cogent3
Pull request #2081 (2024-10-31 05:28:09)

ENH: consistent colours for legend groups
[NEW] plotly chooses random colours for unless otherwise specified.
    For feature types explicitly identified in drawable we have set colours.
    I've adapted this to use a randomly selected colour from the Viridis colour map
    for each novel legend group.

## Summary by Sourcery

Enhance the drawable module to assign consistent colors to novel legend groups using the Viridis color map, and add a test to ensure this functionality works as expected.

Enhancements:
- Implement consistent color assignment for novel legend groups using the Viridis color map in drawable.

Tests:
- Add a test to verify consistent color assignment for novel legend groups.


Patch:
diff --git a/src/cogent3/draw/drawable.py b/src/cogent3/draw/drawable.py
index 8416a63b1..570745980 100644
--- a/src/cogent3/draw/drawable.py
+++ b/src/cogent3/draw/drawable.py
@@ -855,7 +855,7 @@ class _MakeShape:
         kwargs |= dict(reverse=reverse)
 
         klass = self._shapes.get(type_.lower(), Rectangle)
-        color = self._colors.get(type_.lower())
+        color = self.get_colour(type_)
         if klass != Arrow:
             kwargs.pop("reverse", None)
 
@@ -882,5 +882,15 @@ class _MakeShape:
             )
         return result
 
+    def get_colour(self, label):
+        from plotly.colors import sample_colorscale
+
+        label = label.lower()
+
+        if label not in self._colors:
+            self._colors[label] = sample_colorscale("Viridis", numpy.random.rand())[0]
+
+        return self._colors[label]
+
 
 make_shape = _MakeShape()


Test patch:
diff --git a/tests/test_draw/test_draw_integration.py b/tests/test_draw/test_draw_integration.py
index ab3bf7e22..f7b42921a 100644
--- a/tests/test_draw/test_draw_integration.py
+++ b/tests/test_draw/test_draw_integration.py
@@ -1,5 +1,6 @@
 import pathlib
 import unittest
+import uuid
 
 from numpy.testing import assert_allclose
 
@@ -10,6 +11,7 @@ from cogent3.draw.drawable import (
     Drawable,
     _calc_arrow_width,
     get_domain,
+    make_shape,
 )
 from cogent3.util.union_dict import UnionDict
 
@@ -416,3 +418,12 @@ def test_calculating_arrow_width_adjusted():
     assert aw == 10
     aw = _calc_arrow_width(parent_length=100, feature_width=10, frac=0.05)
     assert aw == 5
+
+
+def test_colour_choice():
+    label = str(uuid.uuid4())
+    assert label not in make_shape._colors
+    shape1 = make_shape(type_=label, coords=[(10, 20)])
+    assert label in make_shape._colors
+    shape2 = make_shape(type_=label, coords=[(30, 60)])
+    assert shape1.fillcolor == shape2.fillcolor == make_shape._colors[label]
Repository: cogent3/cogent3
Pull request #2095 (2024-11-04 22:37:53)

MAINT: stricter check on Sequence.is_annotated()
[CHANGED] return True only if records matching the sequence parent
    name are in the bound annotation_db. Don't Fail if annotation_db
    is None, just return False.

## Summary by Sourcery

Improve the Sequence.is_annotated() method to ensure it returns True only when annotations match the sequence's parent name and handle cases where annotation_db is None by returning False. Update tests to cover these scenarios.

Bug Fixes:
- Fix the behavior of Sequence.is_annotated() to return False instead of raising an AttributeError when annotation_db is None.

Enhancements:
- Enhance Sequence.is_annotated() to return True only if annotations match the sequence's parent name.

Tests:
- Add tests to verify the correct operation of Sequence.is_annotated() when annotations match or do not match the sequence's parent name.


Patch:
diff --git a/src/cogent3/core/new_sequence.py b/src/cogent3/core/new_sequence.py
index 8ffe35011..9eeacd0da 100644
--- a/src/cogent3/core/new_sequence.py
+++ b/src/cogent3/core/new_sequence.py
@@ -1530,9 +1530,10 @@ class Sequence:
         return indel_map, seq
 
     def is_annotated(self):
-        """returns True if sequence has any annotations"""
-        num = self.annotation_db.num_matches() if self.annotation_db else 0
-        return num != 0
+        """returns True if sequence parent name has any annotations"""
+        with contextlib.suppress(AttributeError):
+            return self.annotation_db.num_matches(seqid=self._seq.seqid) != 0
+        return False
 
     def annotate_matches_to(
         self, pattern: str, biotype: str, name: str, allow_multiple: bool = False


Test patch:
diff --git a/tests/test_core/test_new_sequence.py b/tests/test_core/test_new_sequence.py
index e4b3bc77e..66df2bef3 100644
--- a/tests/test_core/test_new_sequence.py
+++ b/tests/test_core/test_new_sequence.py
@@ -867,14 +867,22 @@ def test_strand_symmetry():
 
 def test_is_annotated():
     """is_annotated operates correctly"""
-    s = new_moltype.DNA.make_seq(seq="ACGGCTGAAGCGCTCCGGGTTTAAAACG")
-    if hasattr(s, "annotation_db"):
-        assert not s.is_annotated()
-        _ = s.add_feature(biotype="gene", name="blah", spans=[(0, 10)])
-        assert s.is_annotated()
-    else:
-        with pytest.raises(AttributeError):
-            s.is_annotated()
+    s = new_moltype.DNA.make_seq(seq="ACGGCTGAAGCGCTCCGGGTTTAAAACG", name="s1")
+    _ = s.add_feature(biotype="gene", name="blah", spans=[(0, 10)])
+    assert s.is_annotated()
+
+
+def test_not_is_annotated():
+    """is_annotated operates correctly"""
+    s = new_moltype.DNA.make_seq(seq="ACGGCTGAAGCGCTCCGGGTTTAAAACG", name="s1")
+    assert not s.is_annotated()
+    # annotation on different seq
+    s.annotation_db.add_feature(
+        seqid="s2", biotype="gene", name="blah", spans=[(0, 10)]
+    )
+    assert not s.is_annotated()
+    s.annotation_db = None
+    assert not s.is_annotated()
 
 
 def test_to_html():
Repository: cogent3/cogent3
Pull request #2140 (2024-11-26 22:03:27)

BUG: fixed a regression in expanding user paths for loading sequences
## Summary by Sourcery

Fix a regression in user path expansion for sequence loading and introduce a new parameter to support future alignment type changes. Add a test to ensure correct path handling with pathlib.

Bug Fixes:
- Fix a regression in expanding user paths for loading sequences by using pathlib.Path.expanduser().

Enhancements:
- Add a new parameter 'new_type' to the load_aligned_seqs function to support future changes in alignment type.

Tests:
- Add a test to verify loading sequences with user paths expanded using pathlib.


Patch:
diff --git a/src/cogent3/__init__.py b/src/cogent3/__init__.py
index d55f3ec7e..2531f3064 100644
--- a/src/cogent3/__init__.py
+++ b/src/cogent3/__init__.py
@@ -286,6 +286,7 @@ def _load_files_to_unaligned_seqs(
 
 def _load_seqs(file_format, filename, fmt, parser_kw):
     """utility function for loading sequences"""
+    filename = pathlib.Path(filename).expanduser()
     fmt = fmt or file_format
     if not fmt:
         msg = "could not determined file format, set using the format argument"
@@ -487,6 +488,7 @@ def load_aligned_seqs(
     label_to_name=None,
     parser_kw=None,
     info=None,
+    new_type: bool = False,
     **kw,
 ):
     """
@@ -506,6 +508,11 @@ def load_aligned_seqs(
         function for converting original name into another name.
     parser_kw : dict
         optional arguments for the parser
+    new_type
+        if True, the returned Alignment will be of the new type,
+        (cogent3.core.new_alignment.Alignment). The default will be
+        changed to True in 2024.12. Support for the old style will be removed
+        as of 2025.6.
     kw
         passed to make_aligned_seqs
 
@@ -525,6 +532,7 @@ def load_aligned_seqs(
         moltype=moltype,
         source=filename,
         info=info,
+        new_type=new_type,
         **kw,
     )
 


Test patch:
diff --git a/tests/test_core/test_new_alignment.py b/tests/test_core/test_new_alignment.py
index a9f834736..67aebe5ed 100644
--- a/tests/test_core/test_new_alignment.py
+++ b/tests/test_core/test_new_alignment.py
@@ -5219,3 +5219,25 @@ def test_deserialise_alignment():
     assert aln.names == ["new_seq1", "new_seq2"]
     assert str(aln.get_seq("new_seq1")) == "ATCG"
     assert str(aln.get_seq("new_seq2")) == "TAGC"
+
+
+@pytest.fixture
+def home_seqs(DATA_DIR) -> str:
+    """makes a temporary directory with file"""
+    import tempfile
+
+    HOME = pathlib.Path("~").expanduser()
+    fn = "brca1.fasta"
+    contents = (DATA_DIR / fn).read_text()
+    with tempfile.TemporaryDirectory(dir=HOME) as dn:
+        dn = pathlib.Path(dn)
+        outpath = HOME / dn.name / fn
+        outpath.expanduser().write_text(contents)
+        yield f"~/{dn.name}/{fn}"
+
+
+@pytest.mark.parametrize("mk_cls", [load_aligned_seqs, load_unaligned_seqs])
+def test_load_with_pathlib(mk_cls, home_seqs):
+    path = home_seqs
+    got = mk_cls(path, moltype="dna")
+    assert "Human" in got.names
Repository: cogent3/cogent3
Pull request #2194 (2025-01-16 02:56:30)

BUG: KmerAlphabet.to_indices uses alphabet dtype
## Summary by Sourcery

Fix KmerAlphabet.to_indices to use the alphabet's dtype and add tests to ensure correct dtype handling for sequences.

Bug Fixes:
- Fix KmerAlphabet.to_indices to use the alphabet's dtype instead of a dynamically determined array type.

Tests:
- Add test to verify that KmerAlphabet.to_indices maintains the correct dtype for both small and full sequences.


Patch:
diff --git a/src/cogent3/core/new_alphabet.py b/src/cogent3/core/new_alphabet.py
index f90c3322c..58a9a3392 100644
--- a/src/cogent3/core/new_alphabet.py
+++ b/src/cogent3/core/new_alphabet.py
@@ -980,7 +980,7 @@ class KmerAlphabet(tuple, AlphabetABC, KmerAlphabetABC):
         size = len(seq) - self.k + 1
         if independent_kmer:
             size = int(numpy.ceil(size / self.k))
-        result = numpy.zeros(size, dtype=get_array_type(size))
+        result = numpy.zeros(size, dtype=self.dtype)
         gap_char_index = self.monomers.gap_index or -1
         gap_index = self.gap_index or -1
         return seq_to_kmer_indices(


Test patch:
diff --git a/tests/test_core/test_new_alphabet.py b/tests/test_core/test_new_alphabet.py
index db48f1f17..1ec89acd2 100644
--- a/tests/test_core/test_new_alphabet.py
+++ b/tests/test_core/test_new_alphabet.py
@@ -771,3 +771,18 @@ def test_pickling_alphabet(alpha):
     ser = pickle.dumps(alpha)
     got = pickle.loads(ser)
     assert got == alpha
+
+
+@pytest.mark.parametrize(
+    "alpha",
+    [
+        new_moltype.DNA.alphabet.get_kmer_alphabet(k=3),
+        new_genetic_code.DEFAULT.get_alphabet(include_stop=False),
+    ],
+)
+def test_trinucs_to_indices_dtype(alpha):
+    s = numpy.array([2, 0, 3] * 1000, dtype=numpy.uint8)
+    small = alpha.to_indices(s[:24])
+    assert len(small) == len(small.tobytes())
+    full = alpha.to_indices(s)
+    assert len(full) == len(full.tobytes())
Repository: cogent3/cogent3
Pull request #2203 (2025-01-29 05:35:47)

TST: added test and more informative error message for mixed case input seqs
## Summary by Sourcery

Tests:
- Added tests for mixed-case input sequences and sequences with invalid characters.


Patch:
diff --git a/src/cogent3/core/new_moltype.py b/src/cogent3/core/new_moltype.py
index ff38d063d..3f7095a7f 100644
--- a/src/cogent3/core/new_moltype.py
+++ b/src/cogent3/core/new_moltype.py
@@ -645,10 +645,13 @@ class MolType:
         if isinstance(seq, str):
             seq = self.coerce_to(seq.encode("utf8")) if self.coerce_to else seq
 
-        if check_seq:
-            assert self.is_valid(
-                seq,
-            ), f"{seq[:4]!r} not valid for moltype {self.name!r}"
+        if check_seq and not self.is_valid(seq):
+            alpha = self.most_degen_alphabet()
+            s = alpha.from_indices(seq)
+            values = tuple(set(s) - set(alpha))
+            msg = f"{values} not valid for moltype {self.name!r} alphabet {alpha}"
+            raise new_alphabet.AlphabetError(msg)
+
         seq = "" if seq is None else seq
         return self._make_seq(moltype=self, seq=seq, name=name, **kwargs)
 


Test patch:
diff --git a/tests/test_core/test_new_sequence.py b/tests/test_core/test_new_sequence.py
index 5f4825267..3528d7a50 100644
--- a/tests/test_core/test_new_sequence.py
+++ b/tests/test_core/test_new_sequence.py
@@ -2963,8 +2963,7 @@ def aa_moltype(DATA_DIR, tmp_path):
 
 
 def test_load_invalid_moltype(aa_moltype):
-    with pytest.raises(AssertionError):
-        # this should be an AlphabetError
+    with pytest.raises(new_alphabet.AlphabetError):
         cogent3.load_seq(aa_moltype, moltype="dna", new_type=True)
 
 
@@ -3016,6 +3015,16 @@ def test_make_seq_from_types(raw_seq):
     assert str(seq) == "GGTAC"
 
 
+@pytest.mark.parametrize(
+    "raw_seq",
+    ["GGTac", b"GGTac", numpy.array([3, 3, 0, 23, 43], dtype=numpy.uint8)],
+)
+def test_make_seq_invalid(raw_seq):
+    # seq only valid if all upper case
+    with pytest.raises(new_alphabet.AlphabetError):
+        new_moltype.DNA.make_seq(seq=raw_seq)
+
+
 @pytest.mark.parametrize(("moltype", "seq"), [("dna", "AUGC"), ("rna", "ATGC")])
 def test_coerce_moltype(moltype, seq):
     seq = cogent3.make_seq(seq=seq, moltype=moltype, new_type=True)
Repository: cogent3/cogent3
Pull request #2219 (2025-02-24 02:31:26)

ENH: added method for new_genetic_code.GenetCode.is_start()
## Summary by Sourcery

Implements a method to check if a codon is a start codon and adds corresponding tests.

New Features:
- Added a method `is_start()` to the `GenetCode` class that checks if a codon is a start codon.

Tests:
- Added tests for the new `is_start()` method in the `GenetCode` class.


Patch:
diff --git a/src/cogent3/core/new_genetic_code.py b/src/cogent3/core/new_genetic_code.py
index 46f4ce911..0118f5d6f 100644
--- a/src/cogent3/core/new_genetic_code.py
+++ b/src/cogent3/core/new_genetic_code.py
@@ -300,10 +300,15 @@ class GeneticCode:
         for strand, start in itertools.product(("+", "-"), range(3)):
             yield strand, start, self.translate(seq, start, rc=strand == "-")
 
-    def is_stop(self, codon):
+    def is_stop(self, codon: str) -> bool:
         """Returns True if codon is a stop codon, False otherwise."""
         return self[codon] == "*"
 
+    def is_start(self, codon: str) -> bool:
+        """Returns True if codon is a start codon, False otherwise."""
+        fixed_codon = codon.upper().replace("U", "T")
+        return fixed_codon in self.start_codons
+
     @functools.cache
     def get_alphabet(
         self,


Test patch:
diff --git a/tests/test_core/test_new_genetic_code.py b/tests/test_core/test_new_genetic_code.py
index 349177bbd..815327de0 100644
--- a/tests/test_core/test_new_genetic_code.py
+++ b/tests/test_core/test_new_genetic_code.py
@@ -39,6 +39,18 @@ def test_start_codons():
     assert code.start_codons == expect
 
 
+@pytest.mark.parametrize("codon", ["ATG", "AUG", "CUG", "TTG"])
+def test_is_start_true(codon):
+    code = new_genetic_code.DEFAULT
+    assert code.is_start(codon)
+
+
+@pytest.mark.parametrize("codon", ["TCC", "AUU", "AAA"])
+def test_is_start_false(codon):
+    code = new_genetic_code.DEFAULT
+    assert not code.is_start(codon)
+
+
 def test_sense_codons():
     code = new_genetic_code.DEFAULT
     assert len(code.sense_codons) == 61
Repository: cogent3/cogent3
Pull request #2223 (2025-02-28 02:37:45)

fix(moltype): improve moltype validation
### Summary
Fix moltype validation logic to handle edge cases correctly.

### Changes
- Improved moltype validation to check for unexpected input values.
- Updated related test cases.

### Reason
Previously, the validation did not handle some special cases, which could lead to incorrect classification.

### Impact
- Affects `moltype` validation module.
- No breaking changes expected.

### Test
- Added new test cases to cover edge cases.
- Manually tested with various moltype inputs.

## Summary by Sourcery

Improves moltype validation to check for unexpected input values and raises an error if the moltype is invalid.

Bug Fixes:
- Fixes an issue where the moltype validation logic did not handle unexpected input values, leading to incorrect classification.

Tests:
- Adds a new test case to cover the edge case of invalid moltype inputs.


Patch:
diff --git a/src/cogent3/app/sample.py b/src/cogent3/app/sample.py
index 555e4e481..52a04f742 100644
--- a/src/cogent3/app/sample.py
+++ b/src/cogent3/app/sample.py
@@ -968,9 +968,12 @@ class omit_bad_seqs:
         """
         if moltype:
             moltype = cogent3.get_moltype(moltype)
-        assert moltype.label.lower() in "dna rna protein protein_with_stop", (
-            "moltype must be one of DNA, RNA or PROTEIN"
-        )
+        valid_moltypes = {"dna", "rna", "protein", "protein_with_stop"}
+        if moltype.label.lower() not in valid_moltypes:
+            raise new_moltype.MolTypeError(
+                f"Invalid moltype: {moltype.label}. Moltype must be one of DNA, RNA, PROTEIN, or PROTEIN_WITH_STOP."
+            )
+
         # refactor: design, this should raise a MolTypeError
         self._quantile = quantile
         self._gap_fraction = gap_fraction


Test patch:
diff --git a/tests/test_app/test_sample.py b/tests/test_app/test_sample.py
index 13c9b99c1..ec5c23856 100644
--- a/tests/test_app/test_sample.py
+++ b/tests/test_app/test_sample.py
@@ -6,6 +6,7 @@ import pytest
 import cogent3
 from cogent3.app import composable, sample
 from cogent3.app.composable import NotCompleted
+from cogent3.core.new_moltype import MolTypeError
 
 DNA = cogent3.get_moltype("dna")
 
@@ -499,6 +500,11 @@ def test_omit_bad_seqs(bad_gap_data, gap_fraction, quantile, expected_keys):
     assert got.to_dict() == expected
 
 
+def test_omit_bad_seqs_error():
+    with pytest.raises(MolTypeError):
+        sample.omit_bad_seqs(moltype="text")
+
+
 @pytest.fixture
 def bad_ambig_gap_data():
     return {