Repository: cogent3/cogent3
Pull request #1200 (2022-11-08 00:41:32)

ENH: improved robustness of composable apps


Patch:
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index c5523a167..dca0e379c 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -953,6 +953,9 @@ def _call(self, val, *args, **kwargs):
 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 isinstance(data, NotCompleted):
+        return data
+
     if not self._data_types or self._data_types & {
         "SerialisableType",
         "IdentifierType",
diff --git a/src/cogent3/app/typing.py b/src/cogent3/app/typing.py
index 872765ca2..11d175a69 100644
--- a/src/cogent3/app/typing.py
+++ b/src/cogent3/app/typing.py
@@ -84,7 +84,9 @@ def get_constraint_names(*hints) -> set[str, ...]:
     """returns the set of named constraints of a type hint"""
     all_hints = set()
     for hint in hints:
-        if hint in (SerialisableType, IdentifierType) or inspect.isclass(hint):
+        if hint in (SerialisableType, IdentifierType) or (
+            inspect.isclass(hint) and get_origin(hint) not in (list, tuple, set)
+        ):
             all_hints.add(hint.__name__)
             continue
 
@@ -96,7 +98,7 @@ def get_constraint_names(*hints) -> set[str, ...]:
             all_hints.update(hint.__constraints__)
             continue
 
-        if get_origin(hint) in (Union, list, tuple):
+        if get_origin(hint) in (Union, list, tuple, set):
             all_hints.update(get_constraint_names(*get_args(hint)))
 
         if type(hint) == type:


Test patch:
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index 62d0abf7f..5658963fa 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -982,5 +982,23 @@ def test_handles_None():
     __app_registry.pop(get_object_provenance(none_out), None)
 
 
+def test_validate_data_type_not_completed_pass_through():
+    # returns the instance of a NotCompleted created by an input
+    @define_app
+    def take_int1(val: int) -> int:
+        return NotCompleted("ERROR", "take_int1", "external to app", source="unknown")
+
+    @define_app
+    def take_int2(val: int) -> int:
+        return val
+
+    app = take_int1() + take_int2()
+    got = app(2)
+    assert got.origin == "take_int1"
+
+    __app_registry.pop(get_object_provenance(take_int1), None)
+    __app_registry.pop(get_object_provenance(take_int2), None)
+
+
 if __name__ == "__main__":
     main()
diff --git a/tests/test_app/test_typing.py b/tests/test_app/test_typing.py
index 18fc6d710..ae41aa2be 100644
--- a/tests/test_app/test_typing.py
+++ b/tests/test_app/test_typing.py
@@ -1,4 +1,6 @@
-from typing import List, Tuple, Union
+import sys
+
+from typing import List, Set, Tuple, Union
 
 import pytest
 
@@ -93,12 +95,22 @@ def test_hints_resolved_from_str():
     assert got == {"SerialisableType", "DnaSequence"}
 
 
-@pytest.mark.parametrize("container", (List, Tuple))
+@pytest.mark.parametrize("container", (List, Tuple, Set))
 def test_hints_from_container_type(container):
     got = get_constraint_names(container[AlignedSeqsType])
     assert got == {"Alignment", "ArrayAlignment"}
 
 
+@pytest.mark.skipif(
+    (sys.version_info.major, sys.version_info.minor) == (3, 8),
+    reason="type object subscripting supported in >= 3.9",
+)
+@pytest.mark.parametrize("container", (list, set, tuple))
+def test_hints_from_container_type_obj(container):
+    got = get_constraint_names(container[AlignedSeqsType])
+    assert got == {"Alignment", "ArrayAlignment"}
+
+
 def test_hint_inherited_class():
     from collections.abc import MutableSequence