nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: cogent3/cogent3
Pull request #2290 (2025-04-28 22:28:30)

MAINT: make repr of SequenceCollection and Alignment consistent
## Summary by Sourcery

Enhancements:
- Update the `SequenceCollection` `repr` format to place the moltype and class name before the sequence list, separated by a colon.


Patch:
diff --git a/src/cogent3/core/new_alignment.py b/src/cogent3/core/new_alignment.py
index 9650af3b8..aad2ad62c 100644
--- a/src/cogent3/core/new_alignment.py
+++ b/src/cogent3/core/new_alignment.py
@@ -2184,7 +2184,7 @@ class SequenceCollection:
 
         seqs = ", ".join(seqs)
 
-        return f"{len(self.names)}x ({seqs}) {self.moltype.label} seqcollection"
+        return f"{len(self.names)}x {self.moltype.label} seqcollection: ({seqs})"
 
     def _repr_html_(self) -> str:
         settings = self._repr_policy.copy()


Test patch:
diff --git a/tests/test_core/test_new_alignment.py b/tests/test_core/test_new_alignment.py
index 57bcdd41c..a858b9dcb 100644
--- a/tests/test_core/test_new_alignment.py
+++ b/tests/test_core/test_new_alignment.py
@@ -790,7 +790,7 @@ def test_sequence_collection_repr():
     seqs = new_alignment.make_unaligned_seqs(data, moltype="dna")
     assert (
         repr(seqs)
-        == "2x (ENSMUSG00000039616[GCCCTTCAAA...], ENSMUSG00000056468[GCCAGGGGGA...]) dna seqcollection"
+        == "2x dna seqcollection: (ENSMUSG00000039616[GCCCTTCAAA...], ENSMUSG00000056468[GCCAGGGGGA...])"
     )
 
     data = {
@@ -800,20 +800,24 @@ def test_sequence_collection_repr():
     seqs = new_alignment.make_unaligned_seqs(data, moltype="dna")
     assert (
         repr(seqs)
-        == "2x (ENSMUSG00000039616[GCCCTTCAAA...], ENSMUSG00000056468[GCCAGGGGGA...]) dna seqcollection"
+        == "2x dna seqcollection: (ENSMUSG00000039616[GCCCTTCAAA...], ENSMUSG00000056468[GCCAGGGGGA...])"
     )
 
     data = {
         "a": "TCGAT",
     }
     seqs = new_alignment.make_unaligned_seqs(data, moltype="dna")
-    assert repr(seqs) == "1x (a[TCGAT]) dna seqcollection"
+    assert repr(seqs) == "1x dna seqcollection: (a[TCGAT])"
 
     data = {
         "a": "TCGAT" * 2,
     }
     seqs = new_alignment.make_unaligned_seqs(data, moltype="dna")
-    assert repr(seqs) == "1x (a[TCGATTCGAT]) dna seqcollection"
+    assert repr(seqs) == "1x dna seqcollection: (a[TCGATTCGAT])"
+
+    data = {"a": "", "b": ""}
+    seqs = new_alignment.make_unaligned_seqs(data, moltype="dna")
+    assert repr(seqs) == "2x dna seqcollection: (a[], b[])"
 
 
 def test_sequence_collection_set_wrap_affects_repr_html():
Repository: cogent3/cogent3
Pull request #2310 (2025-05-12 04:11:22)

MAINT: make the seq storage to_alphabet() exception types consistent
[CHANGED] SeqsData.to_alphabet() and AlignedSeqsData.to_alphabet() now
    both raise an AlphabetError if data inconsistent

## Summary by Sourcery

Unify exception handling in sequence storage to_alphabet() methods by consistently raising AlphabetError for data inconsistencies and update related tests accordingly.

Bug Fixes:
- Standardize exception type to AlphabetError for inconsistent data in to_alphabet() methods.

Tests:
- Update tests to expect AlphabetError instead of MolTypeError for to_alphabet() failures.


Patch:
diff --git a/src/cogent3/core/new_alignment.py b/src/cogent3/core/new_alignment.py
index 7dbd49dc8..b5de724ab 100644
--- a/src/cogent3/core/new_alignment.py
+++ b/src/cogent3/core/new_alignment.py
@@ -589,9 +589,7 @@ class SeqsData(SeqsDataABC):
                     f"Changing from old alphabet={self.alphabet} to new "
                     f"{alphabet=} is not valid for this data"
                 )
-                raise new_alphabet.AlphabetError(
-                    msg,
-                )
+                raise new_alphabet.AlphabetError(msg)
             new_data[seqid] = as_new_alpha
 
         return self.copy(
@@ -4103,9 +4101,7 @@ class AlignedSeqsData(AlignedSeqsDataABC):
                     f"Changing from old alphabet={self.alphabet} to new "
                     f"{alphabet=} is not valid for this data"
                 )
-                raise new_moltype.MolTypeError(
-                    msg,
-                )
+                raise new_alphabet.AlphabetError(msg)
             gapped[i] = as_new_alpha
 
         return self.__class__(


Test patch:
diff --git a/tests/test_core/test_new_alignment.py b/tests/test_core/test_new_alignment.py
index d01c2e144..e703fc470 100644
--- a/tests/test_core/test_new_alignment.py
+++ b/tests/test_core/test_new_alignment.py
@@ -2208,7 +2208,7 @@ def test_to_dna():
     assert dna.moltype.label == "dna"
     # should fail if invalid character set
     paln = dna.get_translation()
-    with pytest.raises(new_moltype.MolTypeError):
+    with pytest.raises(new_alphabet.AlphabetError):
         _ = paln.to_dna()
Repository: cogent3/cogent3
Pull request #2383 (2025-06-28 06:36:10)

BUG: support pickling of new type seq collections
[FIXED] the MappingProxyType class cannot be pickled,
     so this required custom methods to support pickling.

## Summary by Sourcery

Enable pickling support for the new aligned and unaligned sequence collection types and add tests to verify correct serialization and deserialization.

Bug Fixes:
- Fix pickling of sequence collection classes by implementing custom __getstate__ and __setstate__ methods to restore MappingProxyType for the name_map.

Tests:
- Add parametrized tests to ensure aligned and unaligned sequence collections round-trip through pickle preserving names and sequence data.


Patch:
diff --git a/src/cogent3/core/new_alignment.py b/src/cogent3/core/new_alignment.py
index 7487cd11b..b7114f09f 100644
--- a/src/cogent3/core/new_alignment.py
+++ b/src/cogent3/core/new_alignment.py
@@ -702,6 +702,15 @@ class SequenceCollection(AnnotatableMixin):
         # override in subclasses
         self._seqs = _IndexableSeqs(self, make_seq=self._make_seq)
 
+    def __getstate__(self) -> dict:
+        return self._get_init_kwargs()
+
+    def __setstate__(self, state: dict) -> None:
+        state["name_map"] = types.MappingProxyType(state.pop("name_map"))
+        obj = self.__class__(**state)
+
+        self.__dict__.update(obj.__dict__)
+
     def _make_seq(self, name: str) -> new_sequence.Sequence:
         # seqview is given the name of the parent (if different from the current name)
         # the sequence is given the current name


Test patch:
diff --git a/tests/test_core/test_new_alignment.py b/tests/test_core/test_new_alignment.py
index 3d06abb7e..7aeb404ee 100644
--- a/tests/test_core/test_new_alignment.py
+++ b/tests/test_core/test_new_alignment.py
@@ -6123,6 +6123,20 @@ def test_renamed_deepcopy(renamed_aln):
     assert set(got.names) == {k.upper() for k in data}
 
 
+@pytest.mark.parametrize(
+    "mk_cls", [new_alignment.make_unaligned_seqs, new_alignment.make_aligned_seqs]
+)
+def test_pickling_seqcoll(mk_cls):
+    import pickle
+
+    raw = {"s1": "ACG-T", "s2": "ACGGT"}
+    coll = mk_cls(raw, moltype="dna")
+    pickled = pickle.dumps(coll)
+    unpickled = pickle.loads(pickled)
+    assert set(unpickled.names) == {k.lower() for k in raw}
+    assert unpickled.to_dict() == raw
+
+
 def test_alignment_copy_handling_annot_db():
     """Test that copying an alignment works correctly."""
     data = {
Repository: cogent3/cogent3
Pull request #2385 (2025-06-30 00:40:02)

MAINT: load_db defaults to using obj.source
[CHANGED] don't inject identifier into info dict as that is not
    json compatible

## Summary by Sourcery

Improve load_db behavior to respect existing source attributes, avoid injecting identifiers into the info dictionary for JSON compatibility, and maintain exception context while adding a test to validate the new loading logic.

Bug Fixes:
- Prevent injection of the record identifier into the info dict to avoid JSON compatibility issues

Enhancements:
- Change load_db to prefer an object's own source attribute and derive a default source path when missing
- Chain the original AttributeError when deserialisation fails to preserve context

Tests:
- Add test to verify load_db does not add a source entry when the object already provides a source attribute


Patch:
diff --git a/src/cogent3/app/io.py b/src/cogent3/app/io.py
index 710de3d0b..45f66c055 100644
--- a/src/cogent3/app/io.py
+++ b/src/cogent3/app/io.py
@@ -516,16 +516,15 @@ class load_db:
         """returns deserialised object"""
         try:
             data = identifier.read()
-        except AttributeError:
+        except AttributeError as e:
             msg = f"{identifier} failed because its of type {type(identifier)}"
-            raise AttributeError(
-                msg,
-            )
+            raise AttributeError(msg) from e
 
         # do we need to inject identifier attribute?
         result = self.deserialiser(data)
-        if hasattr(result, "info"):
-            result.info["source"] = result.info.get("source", identifier)
+        if not hasattr(result, "source") and hasattr(result, "info"):
+            src = Path(identifier.data_store.source) / identifier.unique_id
+            result.info["source"] = result.info.get("source", str(src))
         else:
             with contextlib.suppress(AttributeError):
                 identifier = getattr(result, "source", identifier)


Test patch:
diff --git a/tests/test_app/test_io.py b/tests/test_app/test_io.py
index cd2d5ce5e..dd8b449ee 100644
--- a/tests/test_app/test_io.py
+++ b/tests/test_app/test_io.py
@@ -473,6 +473,29 @@ def test_write_db_load_db(fasta_dir, tmp_dir):
     assert data_store.record_type == get_object_provenance(orig)
 
 
+def test_load_db_prefer_source_attr(tmp_dir):
+    from cogent3.app.sqlite_data_store import DataStoreSqlite
+
+    path = tmp_dir / "test.sqlitedb"
+    data_store = DataStoreSqlite(path, mode="w")
+
+    seqs = cogent3.make_unaligned_seqs(
+        data={"a": "ACGG", "b": "GGC"},
+        moltype="dna",
+        info={"demo": "dummy"},
+        new_type=True,
+        source="blah.1.2.fa",
+    )
+    writer = io_app.write_db(data_store=data_store)
+    writer(seqs)  # pylint: disable=not-callable
+    data_store.close()
+    data_store = open_data_store(path)
+    # this old style rich dict will
+    loader = io_app.load_db()
+    got = loader(data_store[0])  # pylint: disable=not-callable
+    assert "source" not in got.info
+
+
 def test_write_read_db_not_completed(tmp_dir):
     from cogent3.app.sqlite_data_store import DataStoreSqlite