Repository: cogent3/cogent3
Pull request #1125 (2022-08-22 01:51:18)

BUG: fix bug in _new
[FIXED] was not correctly handling variable positional arguments.
    Use the inspect Signature.bind_partial method to solve this correctly!


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index 4e6aca286..d42048e33 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -857,22 +857,12 @@ def _new(klass, *args, **kwargs):
     obj = object.__new__(klass)
 
     init_sig = inspect.signature(klass.__init__)
-    no_default = inspect.Parameter.empty
-    arg_order = list(init_sig.parameters)[1:]  # exclude "self"
-
-    kw_args = {
-        k: p.default
-        for k, p in init_sig.parameters.items()
-        if k != "self" and (p.default is not no_default)
-    }
-
-    obj._init_vals = {}
-    if arg_order:
-        for i, v in enumerate(args):
-            k, v = arg_order[i], v
-            obj._init_vals[k] = v
-
-    obj._init_vals = {**obj._init_vals, **kw_args, **kwargs}
+    bargs = init_sig.bind_partial(klass, *args, **kwargs)
+    bargs.apply_defaults()
+    init_vals = bargs.arguments
+    init_vals.pop("self")
+
+    obj._init_vals = init_vals
     return obj
 
 


Test patch:
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index a5ecf87c6..80930f7a4 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -531,5 +531,50 @@ def test_composed_func_pickleable():
     assert unpickled.input is not None
 
 
+def test_composable_new1():
+    """correctly associate argument vals with their names when have variable
+    positional args"""
+    from cogent3.app.composable import __app_registry
+    from cogent3.util.misc import get_object_provenance
+
+    @define_app
+    class pos_var_pos1:
+        def __init__(self, a, b, *args):
+            self.a = a
+            self.b = b
+            self.args = args
+
+        def main(self, val: int) -> int:
+            return val
+
+    instance = pos_var_pos1(2, 3, 4, 5, 6)
+    assert instance._init_vals == {"a": 2, "b": 3, "args": (4, 5, 6)}
+
+    p = get_object_provenance(pos_var_pos1)
+    __app_registry.pop(p)
+
+
+def test_composable_new2():
+    """correctly associate argument vals with their names when have variable
+    positional args and kwargs"""
+    from cogent3.app.composable import __app_registry
+    from cogent3.util.misc import get_object_provenance
+
+    @define_app
+    class pos_var_pos_kw2:
+        def __init__(self, a, *args, c=False):
+            self.a = a
+            self.c = c
+            self.args = args
+
+        def main(self, val: int) -> int:
+            return val
+
+    instance = pos_var_pos_kw2(2, 3, 4, 5, 6, c=True)
+    assert instance._init_vals == {"a": 2, "args": (3, 4, 5, 6), "c": True}
+    p = get_object_provenance(pos_var_pos_kw2)
+    __app_registry.pop(p)
+
+
 if __name__ == "__main__":
     main()
diff --git a/tests/test_app/test_evo.py b/tests/test_app/test_evo.py
index 5d3caf1ed..a35219cbe 100644
--- a/tests/test_app/test_evo.py
+++ b/tests/test_app/test_evo.py
@@ -274,10 +274,10 @@ class TestModel(TestCase):
             "name=None, optimise_motif_probs=False, sm_args=None, lf_args=None, "
             "time_het=None, param_rules=None, opt_args=None, upper=50, "
             "split_codons=False, show_progress=False, verbose=False), "
-            "alternates=model(sm='HKY85', tree=None, unique_trees=False, "
+            "alternates=(model(sm='HKY85', tree=None, unique_trees=False, "
             "name='hky85-max-het', optimise_motif_probs=False, sm_args=None, lf_args=None, "
             "time_het='max', param_rules=None, opt_args=None, upper=50,"
-            " split_codons=False, show_progress=False, verbose=False),"
+            " split_codons=False, show_progress=False, verbose=False),),"
             " sequential=True, init_alt=None)"
         )
         self.assertEqual(got, expect)
diff --git a/tests/test_app/test_sample.py b/tests/test_app/test_sample.py
index c8966da01..dea792642 100644
--- a/tests/test_app/test_sample.py
+++ b/tests/test_app/test_sample.py
@@ -123,6 +123,11 @@ class TranslateTests(TestCase):
         with self.assertRaises(AssertionError):
             _ = sample.take_codon_positions(moltype=None)
 
+    def test_take_named_3(self):
+        """3 named seqs"""
+        select = sample.take_named_seqs("a", "b", "c")
+        assert select._init_vals == {"names": tuple("abc"), "negate": False}
+
     def test_take_named(self):
         """returns collections containing named seqs"""
         select = sample.take_named_seqs("a", "b")