Repository: cogent3/cogent3
Pull request #1123 (2022-08-17 12:50:48)

Composable refactor


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index 17a7729bc..4e6aca286 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -14,11 +14,7 @@ from typing import Tuple
 
 from scitrack import CachingLogger
 
-from cogent3.app.typing import (
-    IdentifierType,
-    SerialisableType,
-    get_constraint_names,
-)
+from cogent3.app.typing import get_constraint_names
 from cogent3.util import parallel as PAR
 from cogent3.util import progress_display as UI
 from cogent3.util.misc import (
@@ -826,10 +822,14 @@ def _disconnect(self):
 
 def _add(self, other):
     # Check order
-    if self.app_type is WRITER:
+    if isinstance(self, user_function) or isinstance(other, user_function):
+        pass
+    elif self.app_type is WRITER:
         raise TypeError("Left hand side of add operator must not be of type writer")
     elif other.app_type is LOADER:
         raise TypeError("Right hand side of add operator must not be of type loader")
+    if self._return_types & {"SerialisableType", "IdentifierType"}:
+        pass
     # validate that self._return_types ia a non-empty set.
     elif not self._return_types:
         raise TypeError(f"return type not defined for {self.__class__.__name__!r}")
@@ -924,7 +924,10 @@ def _setstate(self, data):
 def _validate_data_type(self, data):
     """checks data class name matches defined compatible types"""
     # todo when move to python 3.8 define protocol checks for the two singular types
-    if not self._data_types or self._data_types <= {SerialisableType, IdentifierType}:
+    if not self._data_types or self._data_types & {
+        "SerialisableType",
+        "IdentifierType",
+    }:
         return True
 
     class_name = data.__class__.__name__
diff --git a/src/cogent3/app/typing.py b/src/cogent3/app/typing.py
index 3a127ad95..e6e55f066 100644
--- a/src/cogent3/app/typing.py
+++ b/src/cogent3/app/typing.py
@@ -75,7 +75,7 @@ def get_constraint_names(*hints) -> set[str, ...]:
     all_hints = set()
     for hint in hints:
         if hint in (SerialisableType, IdentifierType):
-            all_hints.add(hint)
+            all_hints.add(hint.__name__)
             continue
 
         if getattr(hint, "__bound__", None):


Test patch:
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index 4c5905b4c..5c985d934 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -410,7 +410,6 @@ class TestUserFunction(TestCase):
             repr(u_function_2), "user_function(name='bar', module='test_composable')"
         )
 
-    @pytest.mark.xfail
     def test_user_function_str(self):
         u_function_1 = user_function(self.foo, "aligned", "aligned")
         u_function_2 = user_function(self.bar, "aligned", "pairwise_distances")
diff --git a/tests/test_app/test_evo.py b/tests/test_app/test_evo.py
index 506dc223b..5d3caf1ed 100644
--- a/tests/test_app/test_evo.py
+++ b/tests/test_app/test_evo.py
@@ -862,7 +862,6 @@ class TestBootstrap(TestCase):
         result = strapper(aln)
         self.assertIsInstance(result, evo_app.bootstrap_result)
 
-    @pytest.mark.xfail
     def test_bootstrap_composability(self):
         """can be composed with load_db and write_db"""
         m1 = evo_app.model("F81")
diff --git a/tests/test_app/test_init.py b/tests/test_app/test_init.py
index 67bedef76..c300064df 100644
--- a/tests/test_app/test_init.py
+++ b/tests/test_app/test_init.py
@@ -79,7 +79,13 @@ class TestAvailableApps(TestCase):
                 (app1, app2)
                 for app1 in applications
                 for app2 in applications
-                if app1 != app2 and (app1._return_types & app2._data_types != set()) and app1.app_type is not WRITER and app2.app_type is not LOADER
+                if app1 != app2
+                and (
+                    app1._return_types & app2._data_types
+                    or app1._return_types & {"SerialisableType", "IdentifierType"}
+                )
+                and app1.app_type is not WRITER
+                and app2.app_type is not LOADER
             ]
 
             for composable_application_tuple in composable_application_tuples:
@@ -106,7 +112,11 @@ class TestAvailableApps(TestCase):
                 (app1, app2)
                 for app1 in applications
                 for app2 in applications
-                if app1 != app2 and ( (app1._return_types & app2._data_types == set() ) or app1.app_type is WRITER or app2.app_type is LOADER )
+                if app1.app_type is WRITER
+                or app2.app_type is LOADER
+                or app1 != app2
+                and not app1._return_types & app2._data_types
+                and not app1._return_types & {"SerialisableType", "IdentifierType"}
             ]
 
             for incompatible_application_tuple in incompatible_application_tuples:
diff --git a/tests/test_app/test_typing.py b/tests/test_app/test_typing.py
index a15d4ebec..4a854aa68 100644
--- a/tests/test_app/test_typing.py
+++ b/tests/test_app/test_typing.py
@@ -2,6 +2,7 @@ import pytest
 
 from cogent3.app.typing import (
     AlignedSeqsType,
+    IdentifierType,
     SeqsCollectionType,
     SerialisableType,
     TabularType,
@@ -59,7 +60,22 @@ def test_get_constraint_names_serilisable():
     """SerialisableType does not define any compatible types"""
 
     got = get_constraint_names(SerialisableType)
-    assert got == {SerialisableType}
+    assert got == {"SerialisableType"}
+
+
+def test_get_constraint_names_identifiertype():
+    """IdentifierType does not define any compatible types"""
+
+    got = get_constraint_names(IdentifierType)
+    assert got == {"IdentifierType"}
+
+
+def test_get_constraint_names_mixed_serilisable_identifiertype():
+    """SerialisableType does not define any compatible types"""
+    from typing import Union
+
+    got = get_constraint_names(Union[SerialisableType, IdentifierType, AlignedSeqsType])
+    assert got == {"SerialisableType", "IdentifierType", "Alignment", "ArrayAlignment"}
 
 
 def test_hints_from_strings_invalid():