nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: cogent3/cogent3
Pull request #953 (2021-09-30 01:22:26)

BUG: refactor variable_positions() to correctly handle gaps
[FIXED] always report a position as variable if multiple non-gap characters
    are present


Patch:
diff --git a/src/cogent3/core/alignment.py b/src/cogent3/core/alignment.py
index 8c88d709e..236556171 100644
--- a/src/cogent3/core/alignment.py
+++ b/src/cogent3/core/alignment.py
@@ -3117,19 +3117,13 @@ class AlignmentI(object):
             column are ignored.
 
         """
-        seqs = [str(self.named_seqs[n]) for n in self.names]
-        seq1 = seqs[0]
-        positions = list(zip(*seqs[1:]))
+        gaps = set(self.moltype.gaps)
         result = []
-        for (position, (motif1, column)) in enumerate(zip(seq1, positions)):
-            for motif in column:
-                if motif != motif1:
-                    if include_gap_motif:
-                        result.append(position)
-                        break
-                    elif motif != "-" and motif1 != "-":
-                        result.append(position)
-                        break
+        for position, column in enumerate(self.iter_positions()):
+            column = set(column)
+            num_states = len(column) if include_gap_motif else len(column - gaps)
+            if num_states > 1:
+                result.append(position)
 
         return result
 


Test patch:
diff --git a/tests/test_core/test_alignment.py b/tests/test_core/test_alignment.py
index 98781ba23..caa226f11 100644
--- a/tests/test_core/test_alignment.py
+++ b/tests/test_core/test_alignment.py
@@ -2181,9 +2181,14 @@ class AlignmentBaseTests(SequenceCollectionBaseTests):
 
     def test_variable_positions(self):
         """correctly identify variable positions"""
-        new_seqs = {"seq1": "ACGTACGT", "seq2": "ACCGACGT", "seq3": "ACGTACGT"}
+        new_seqs = {"A": "-CG-C", "B": "ACAA?", "C": "GCGAC"}
         aln = self.Class(data=new_seqs, moltype=DNA)
-        self.assertEqual(aln.variable_positions(), [2, 3])
+        self.assertEqual(aln.variable_positions(include_gap_motif=True), [0, 2, 3, 4])
+        self.assertEqual(aln.variable_positions(include_gap_motif=False), [0, 2])
+        new_seqs = {"A": "GCGAC", "B": "GCGAC", "C": "GCGAC"}
+        aln = self.Class(data=new_seqs, moltype=DNA)
+        self.assertEqual(aln.variable_positions(include_gap_motif=True), [])
+        self.assertEqual(aln.variable_positions(include_gap_motif=False), [])
 
     def test_to_type(self):
         """correctly interconvert between alignment types"""
Repository: cogent3/cogent3
Pull request #1013 (2022-02-21 22:30:12)

BUG: writer composable apps apply_to now handles provided logger


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index c25337b39..852a2f236 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -418,18 +418,22 @@ class Composable(ComposableType):
             LOGGER = None
         elif type(logger) == scitrack.CachingLogger:
             LOGGER = logger
+            log_file_path = LOGGER.log_file_path
         elif type(logger) == str:
-            LOGGER = scitrack.CachingLogger
-            LOGGER.log_file_path = logger
+            LOGGER = scitrack.CachingLogger()
+            log_file_path = logger
         else:
-            log_file_path = pathlib.Path(_make_logfile_name(self))
-            src = pathlib.Path(self.data_store.source)
-            log_file_path = src.parent / log_file_path
             LOGGER = scitrack.CachingLogger()
-            LOGGER.log_file_path = str(log_file_path)
+            log_file_path = None
+
         if LOGGER:
+            if not log_file_path and not LOGGER.log_file_path:
+                src = pathlib.Path(self.data_store.source)
+                log_file_path = src.parent / _make_logfile_name(self)
+                LOGGER.log_file_path = str(log_file_path)
             LOGGER.log_message(str(self), label="composable function")
             LOGGER.log_versions(["cogent3"])
+
         results = []
         process = self.input or self
         if self.input:


Test patch:
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index fa736951d..4790d7b51 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -6,6 +6,8 @@ from tempfile import TemporaryDirectory
 from unittest import TestCase, main
 from unittest.mock import Mock
 
+from scitrack import CachingLogger
+
 from cogent3.app import io as io_app
 from cogent3.app import sample as sample_app
 from cogent3.app.composable import (
@@ -169,6 +171,20 @@ class TestComposableBase(TestCase):
             self.assertEqual(len(process.data_store.logs), 1)
             process.data_store.close()
 
+    def test_apply_to_logger(self):
+        """correctly uses user provided logger"""
+        dstore = io_app.get_data_store("data", suffix="fasta", limit=3)
+        with TemporaryDirectory(dir=".") as dirname:
+            LOGGER = CachingLogger()
+            reader = io_app.load_aligned(format="fasta", moltype="dna")
+            min_length = sample_app.min_length(10)
+            outpath = os.path.join(os.getcwd(), dirname, "delme.tinydb")
+            writer = io_app.write_db(outpath)
+            process = reader + min_length + writer
+            r = process.apply_to(dstore, show_progress=False, logger=LOGGER)
+            self.assertEqual(len(process.data_store.logs), 1)
+            process.data_store.close()
+
     def test_apply_to_not_completed(self):
         """correctly creates notcompleted"""
         dstore = io_app.get_data_store("data", suffix="fasta", limit=3)
Repository: cogent3/cogent3
Pull request #1058 (2022-06-15 00:29:14)

ENH: enhance placement of dendrogram scale bars
[NEW] added name="scale_bar" to shape annotation
[CHANGED] position the scale bar outside (above or below) the y-limits
    of the dendrogram


Patch:
diff --git a/src/cogent3/draw/dendrogram.py b/src/cogent3/draw/dendrogram.py
index 2343e2f8c..75798330b 100644
--- a/src/cogent3/draw/dendrogram.py
+++ b/src/cogent3/draw/dendrogram.py
@@ -652,8 +652,14 @@ class Dendrogram(Drawable):
         if not self.scale_bar or self.contemporaneous:
             return None, None
 
+        # place scale bar above / below dendrogram area
+        y_shift = (self.tree.max_y - self.tree.min_y) / 11
         x = self.tree.min_x if "left" in self.scale_bar else self.tree.max_x
-        y = self.tree.min_y if "bottom" in self.scale_bar else self.tree.max_y
+        y = (
+            self.tree.min_y - y_shift
+            if "bottom" in self.scale_bar
+            else self.tree.max_y + y_shift
+        )
         scale = 0.1 * self.tree.max_x
         text = f"{scale:.1e}" if scale < 1e-2 else f"{scale:.2f}"
         shape = {
@@ -663,6 +669,7 @@ class Dendrogram(Drawable):
             "x1": x + scale,
             "y1": y,
             "line": {"color": self._line_color, "width": self._line_width},
+            "name": "scale_bar",
         }
         annotation = UnionDict(
             x=x + (0.5 * scale),


Test patch:
diff --git a/tests/test_draw/test_dendrogram.py b/tests/test_draw/test_dendrogram.py
index 6b630df4f..fc7ab49ef 100644
--- a/tests/test_draw/test_dendrogram.py
+++ b/tests/test_draw/test_dendrogram.py
@@ -178,6 +178,28 @@ class TestDendro(TestCase):
         dnd.tip_font.color = "red"
         self.assertEqual(dnd.tip_font["color"], "red")
 
+    def test_scale_bar_place(self):
+        """outside rectangle containing dendrogram"""
+        from itertools import product
+
+        tree = make_tree(
+            "((a:0.1,b:0.25):0.1,(c:0.02,(e:0.035,f:0.04):0.15):0.3,g:0.3)"
+        )
+        styles = ["square", "circular", "angular", "radial"]
+        for style in styles:
+            dnd = tree.get_figure(style=style)
+            for vert, horizontal in product(["top", "bottom"], ["left", "right"]):
+                dnd.scale_bar = f"{vert} {horizontal}"
+                for s in dnd.figure.layout.shapes:
+                    if s.get("name") == "scale_bar":
+                        scale_y = s["y0"]
+                min_y, max_y = dnd.tree.min_y, dnd.tree.max_y
+                self.assertFalse(min_y <= scale_y <= max_y)
+
+        # if we set scale_bar None, there should be shapes
+        dnd.scale_bar = None
+        self.assertIsNone(dnd.figure.layout.get("shapes"))
+
 
 if __name__ == "__main__":
     main()
Repository: cogent3/cogent3
Pull request #1077 (2022-07-04 23:13:29)

BUG: fixed regression in opening file paths proceeded by ~


Patch:
diff --git a/src/cogent3/util/io.py b/src/cogent3/util/io.py
index 2377cff8a..776129af9 100644
--- a/src/cogent3/util/io.py
+++ b/src/cogent3/util/io.py
@@ -111,6 +111,7 @@ def open_(filename: Union[str, Path], mode="rt", **kwargs) -> IO:
         return open_url(filename, mode=mode, **kwargs)
 
     mode = mode or "rt"
+    filename = Path(filename).expanduser()
     op = _get_compression_open(filename) or open
 
     encoding = kwargs.pop("encoding", None)


Test patch:
diff --git a/tests/test_util/test_io.py b/tests/test_util/test_io.py
index 584a6770f..28764d022 100644
--- a/tests/test_util/test_io.py
+++ b/tests/test_util/test_io.py
@@ -32,8 +32,32 @@ DATADIR = pathlib.Path(__file__).parent.parent / "data"
 
 
 @pytest.fixture
-def tmp_dir(tmpdir_factory):
-    return pathlib.Path(tmpdir_factory.mktemp("test_io"))
+def tmp_dir(tmp_path_factory):
+    return tmp_path_factory.mktemp("test_io")
+
+
+@pytest.fixture
+def home_file() -> str:
+    """makes a temporary directory with file"""
+    import tempfile
+
+    HOME = pathlib.Path("~")
+    fn = "sample.tsv"
+    contents = (DATADIR / fn).read_text()
+    with tempfile.TemporaryDirectory(dir=HOME.expanduser()) as dn:
+        outpath = HOME / pathlib.Path(dn).name / fn
+        outpath.expanduser().write_text(contents)
+        yield str(outpath)
+
+
+@pytest.mark.parametrize("transform", (str, pathlib.Path))
+def test_open_home(home_file, transform):
+    """expands tilde for opening / writing to home"""
+    data_path = DATADIR / "sample.tsv"
+    expect = data_path.read_text()
+    with open_(transform(home_file)) as infile:
+        got = infile.read()
+        assert got == expect
 
 
 def test_does_not_write_if_exception(tmp_dir):
Repository: cogent3/cogent3
Pull request #1112 (2022-08-13 02:11:17)

ENH: add app registry to composable decorator
[NEW] module level dictionary that records apps during decoration
    by their object provenance. This means a 3rd party developer
    could develop an app with the same class name, it would be
    distinguished by it's full provenance.


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index 37a308f30..7d7c9d8c5 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -951,6 +951,9 @@ class _connected:
         self.storage[id(instance)] = value
 
 
+__app_registry = {}
+
+
 def composable(klass=None, *, app_type: AppType = GENERIC):
     app_type = AppType(app_type)
 
@@ -1019,6 +1022,8 @@ def composable(klass=None, *, app_type: AppType = GENERIC):
         if hasattr(klass, "__slots__"):
             klass.__slots__ += tuple(slot_attrs)
 
+        # register this app
+        __app_registry[get_object_provenance(klass)] = klass.__name__
         return klass
 
     return wrapped(klass) if klass else wrapped


Test patch:
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index 5c0970066..c93934af8 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -16,6 +16,7 @@ from cogent3.app.composable import (
     Composable,
     NotCompleted,
     appify,
+    composable,
     user_function,
 )
 from cogent3.app.sample import min_length, omit_degenerates
@@ -475,5 +476,18 @@ class TestUserFunction(TestCase):
         self.assertEqual(ufunc(2), log(4))
 
 
+def test_app_registry():
+    """correctly registers apps"""
+
+    @composable
+    class foo:
+        def main(self, data: int) -> int:
+            return data
+
+    from cogent3.app.composable import __app_registry
+
+    assert __app_registry["test_composable.foo"] == "foo"
+
+
 if __name__ == "__main__":
     main()
Repository: cogent3/cogent3
Pull request #1113 (2022-08-13 08:22:41)

Composable refactor


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index 7d7c9d8c5..f85734d9c 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -1029,6 +1029,11 @@ def composable(klass=None, *, app_type: AppType = GENERIC):
     return wrapped(klass) if klass else wrapped
 
 
+def is_composable(obj):
+    """checks whether obj has been registered by the composable decorator"""
+    return get_object_provenance(obj) in __app_registry
+
+
 def _apply_to(
     self,
     dstore,


Test patch:
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index c93934af8..f387cc2d2 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -480,13 +480,38 @@ def test_app_registry():
     """correctly registers apps"""
 
     @composable
-    class foo:
+    class foo_1:
         def main(self, data: int) -> int:
             return data
 
     from cogent3.app.composable import __app_registry
 
-    assert __app_registry["test_composable.foo"] == "foo"
+    assert __app_registry["test_composable.foo_1"] == "foo_1"
+
+
+def test_app_is_composable():
+    """check is_composable for composable apps"""
+
+    from cogent3.app.composable import is_composable
+
+    @composable
+    class foo_2:
+        def main(self, data: int) -> int:
+            return data
+
+    assert is_composable(foo_2)
+
+
+def test_app_is_not_composable():
+    """check is_composable for non-composable apps"""
+
+    from cogent3.app.composable import is_composable
+
+    class foo_3:
+        def main(self, data: int) -> int:
+            return data
+
+    assert not is_composable(foo_3)
 
 
 if __name__ == "__main__":
diff --git a/tests/test_app/test_init.py b/tests/test_app/test_init.py
index 2bf070770..23f04d6a3 100644
--- a/tests/test_app/test_init.py
+++ b/tests/test_app/test_init.py
@@ -73,7 +73,7 @@ class TestAvailableApps(TestCase):
             applications = _get_all_composables(os.path.join(dirname, "delme"))
 
             for app in applications:
-               self.assertIsInstance(app, Composable)
+                self.assertIsInstance(app, Composable)
 
             composable_application_tuples = [
                 (app1, app2)
@@ -101,7 +101,7 @@ class TestAvailableApps(TestCase):
             applications = _get_all_composables(os.path.join(dirname, "delme"))
 
             for app in applications:
-               self.assertIsInstance(app, Composable)
+                self.assertIsInstance(app, Composable)
 
             incompatible_application_tuples = [
                 (app1, app2)
Repository: cogent3/cogent3
Pull request #1119 (2022-08-16 00:36:14)

BUG: fix __repr__ of composed functions


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index b9b005c23..a392d2eba 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -850,7 +850,7 @@ def _add(self, other):
 def _repr(self):
     val = f"{self.input!r} + " if self.input else ""
     data = ", ".join(f"{k}={v!r}" for k, v in self._init_vals.items())
-    data = f"{self.__class__.__name__}({val}{data})"
+    data = f"{val}{self.__class__.__name__}({data})"
     data = textwrap.fill(data, width=80, break_long_words=False, break_on_hyphens=False)
     return data
 


Test patch:
diff --git a/tests/test_app/test_tree.py b/tests/test_app/test_tree.py
index 979f2ee4c..0b6474aca 100644
--- a/tests/test_app/test_tree.py
+++ b/tests/test_app/test_tree.py
@@ -69,14 +69,13 @@ class TestTree(TestCase):
         tree1 = quick1(dist_matrix)
         self.assertEqual(set(tree1.get_tip_names()), set(aln.names))
 
-    @pytest.mark.xfail
     def test_composable_apps(self):
         """checks the ability of these two apps(fast_slow_dist and quick_tree) to communicate"""
         path = os.path.join(data_path, "brca1_5.paml")
         aln1 = load_aligned_seqs(path, moltype=DNA)
-        fast_slow_dist = dist.fast_slow_dist(fast_calc="hamming", moltype="dna")
+        calc_dist = dist.fast_slow_dist(fast_calc="hamming", moltype="dna")
         quick = tree_app.quick_tree(drop_invalid=False)
-        proc = fast_slow_dist + quick
+        proc = calc_dist + quick
         self.assertEqual(
             str(proc),
             "fast_slow_dist(distance=None, moltype='dna', "
@@ -85,9 +84,6 @@ class TestTree(TestCase):
         )
         self.assertIsInstance(proc, tree_app.quick_tree)
         self.assertIsInstance(proc.input, dist.fast_slow_dist)
-        self.assertIsInstance(proc._input_types, frozenset)
-        self.assertIsInstance(proc._output_types, frozenset)
-        self.assertIsInstance(proc._in, dist.fast_slow_dist)
 
         tree1 = proc(aln1)
         self.assertIsInstance(tree1, PhyloNode)
Repository: cogent3/cogent3
Pull request #1131 (2022-08-25 01:41:30)

MAINT: raise an exception on decorated classes that use slots
[CHANGED] not supporting this yet, possibly never


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index 81cc448fe..083efe9e7 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -992,9 +992,10 @@ def define_app(klass=None, *, app_type: AppType = GENERIC, composable: bool = Tr
             slot_attrs.remove("input")
         else:
             setattr(klass, "input", None)
-        # If a developer has defined slots, we should extend them with our own instance variables.
+
         if hasattr(klass, "__slots__"):
-            klass.__slots__ += tuple(slot_attrs)
+            # not supporting this yet
+            raise NotImplementedError("slots are not currently supported")
 
         # register this app
         __app_registry[get_object_provenance(klass)] = composable


Test patch:
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index fbd43f381..c8d55623d 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -576,5 +576,19 @@ def test_composable_new2():
     __app_registry.pop(p)
 
 
+def test_app_decoration_fails_with_slots():
+    with pytest.raises(NotImplementedError):
+
+        @define_app
+        class not_supported:
+            __slots__ = ("a",)
+
+            def __init__(self, a):
+                self.a = a
+
+            def main(self, val: int) -> int:
+                return val
+
+
 if __name__ == "__main__":
     main()
Repository: cogent3/cogent3
Pull request #1144 (2022-08-30 08:10:24)

ENH: support decorating a function with just positonal arguments


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index f6152c20d..8b903ddf4 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -983,10 +983,10 @@ def _class_from_func(func):
         self._args = args
         self._kwargs = kwargs
 
-    def _main(self, arg, **kwargs):
+    def _main(self, arg, *args, **kwargs):
         kw_args = deepcopy(self._kwargs)
         kw_args = {**kw_args, **kwargs}
-        args = (arg,) + deepcopy(self._args)
+        args = (arg,) + args + deepcopy(self._args)
         bound = self._func_sig.bind(*args, **kw_args)
         return self._user_func(**bound.arguments)
 


Test patch:
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index 49f5ad259..0ace3450e 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -747,5 +747,16 @@ def test_decorated_func_optional():
     __app_registry.pop(get_object_provenance(power), None)
 
 
+def test_decorated_func_just_args():
+    @define_app(composable=False)
+    def power(val: int, pow: int) -> int:
+        return val ** pow
+
+    sqd = power()
+    assert sqd(3, 3) == 27
+
+    __app_registry.pop(get_object_provenance(power), None)
+
+
 if __name__ == "__main__":
     main()
Repository: cogent3/cogent3
Pull request #1151 (2022-09-02 06:22:14)

ENH: support string literal type hints
[NEW] allows foo(name: "some_type")

[CHANGED] add constraints for SeqType


Patch:
diff --git a/src/cogent3/align/progressive.py b/src/cogent3/align/progressive.py
index b58d94705..5c455e8c6 100644
--- a/src/cogent3/align/progressive.py
+++ b/src/cogent3/align/progressive.py
@@ -96,7 +96,6 @@ def TreeAlign(
                 1e-9, edge.length or 0
             )  # catch case where edge has no length
 
-
     LF = model.make_likelihood_function(
         tree.bifurcating(name_unnamed=True), aligned=False
     )
diff --git a/src/cogent3/app/typing.py b/src/cogent3/app/typing.py
index 6d0f7166f..fa8cd2958 100644
--- a/src/cogent3/app/typing.py
+++ b/src/cogent3/app/typing.py
@@ -19,7 +19,15 @@ __status__ = "Alpha"
 AlignedSeqsType = TypeVar("AlignedSeqsType", "Alignment", "ArrayAlignment")
 UnalignedSeqsType = TypeVar("UnalignedSeqsType", bound="SequenceCollection")
 SeqsCollectionType = Union[AlignedSeqsType, UnalignedSeqsType]
-SeqType = TypeVar("SeqType", bound="Sequence")
+SeqType = TypeVar(
+    "SeqType",
+    "Sequence",
+    "DnaSequence",
+    "RnaSequence",
+    "ByteSequence",
+    "ProteinSequence",
+    "ProteinWithStopSequence",
+)
 PairwiseDistanceType = TypeVar("PairwiseDistanceType", bound="DistanceMatrix")
 TabularType = TypeVar("TabularType", "Table", "DictArray", "DistanceMatrix")
 TreeType = TypeVar("TreeType", "TreeNode", "PhyloNode")
@@ -91,6 +99,10 @@ def get_constraint_names(*hints) -> set[str, ...]:
 
         if type(hint) == type:
             all_hints.add(hint.__name__)
+        elif type(hint) == ForwardRef:
+            all_hints.add(hint.__forward_arg__)
+        elif type(hint) == str:
+            all_hints.add(hint)
 
     all_hints = {h.__forward_arg__ if type(h) == ForwardRef else h for h in all_hints}
     return all_hints


Test patch:
diff --git a/tests/test_app/test_typing.py b/tests/test_app/test_typing.py
index 10fe7b4df..2e5153308 100644
--- a/tests/test_app/test_typing.py
+++ b/tests/test_app/test_typing.py
@@ -86,6 +86,13 @@ def test_hints_from_strings():
     assert got == [SerialisableType, AlignedSeqsType]
 
 
+def test_hints_resolved_from_str():
+    got = get_constraint_names("DnaSequence")
+    assert got == {"DnaSequence"}
+    got = get_constraint_names(Union[SerialisableType, "DnaSequence"])
+    assert got == {"SerialisableType", "DnaSequence"}
+
+
 @pytest.mark.parametrize("container", (List, Tuple))
 def test_hints_from_container_type(container):
     got = get_constraint_names(container[AlignedSeqsType])