From ac78e11979aa86836bd9567e9f38de7546dd245b Mon Sep 17 00:00:00 2001 From: nroehner Date: Wed, 16 Feb 2022 14:13:27 -0500 Subject: [PATCH 1/5] First pass of functions for validating an assembly plan. Also added two helper functions to component.by for retrieving subcomponents. --- sbol_utilities/build_planning.py | 142 +++++++++++++++++++++++++++++++ sbol_utilities/component.py | 8 ++ 2 files changed, 150 insertions(+) create mode 100644 sbol_utilities/build_planning.py diff --git a/sbol_utilities/build_planning.py b/sbol_utilities/build_planning.py new file mode 100644 index 00000000..5ab7359b --- /dev/null +++ b/sbol_utilities/build_planning.py @@ -0,0 +1,142 @@ +import sbol3 +import tyto + +from sbol_utilities.component import get_subcomponents, get_subcomponents_by_identity + +# TODO: Change SBOL_ASSEMBLY_PLAN and sbol3.SBOL_DESIGN to tyto calls after resolution of +SBOL_ASSEMBLY_PLAN = 'http://sbols.org/v3#assemblyPlan' +ASSEMBLY_TYPES = {sbol3.SBOL_DESIGN, SBOL_ASSEMBLY_PLAN} + + +def validate_part_in_backbone(pib: sbol3.Component) -> bool: + """Check if a Component represents a part in backbone + + :param plan: Component being validated + :return: true if its structure follows the best practices for representing a part in backbone + """ + subcomps = get_subcomponents(pib) + + has_insert = False + has_backbone = False + + i = 0 + while (not has_insert or not has_backbone) and i < len(subcomps): + part = subcomps[i].instance_of.lookup() + + if tyto.SO.engineered_insert in subcomps[i].roles or tyto.SO.engineered_insert in part.roles: + has_insert = True + elif is_backbone(part): + has_backbone = True + + i = i + 1 + + return has_insert and has_backbone + + +def is_backbone(b: sbol3.Component) -> bool: + """Check if Component is a backbone + + :param plan: Component being checked + :return: true if it has an expected role for a backbone + """ + for role in b.roles: + if role == tyto.SO.vector_replicon or tyto.SO.vector_replicon.is_ancestor_of(role): + return True + + return False + + +def validate_composite_part_assemblies(c: sbol3.Component) -> bool: + """Check if a Component for a composite part has valid assemblies + + :param plan: Component being validated + :return: true if its assemblies follows the best practices for representing a composite part + """ + activities = [g.lookup() for g in c.generated_by] + + invalid_assemblies = [a for a in activities if is_assembly(a) and not validate_assembly(a, c)] + + return len(invalid_assemblies) == 0 + + +def validate_assembly_component(ac: sbol3.Component, composite_part: sbol3.Component) -> bool: + """Check if Component represents the assembly of a composite part + + :param plan: Component being validated and Component for composite part + :return: true if it follows best practices for representing assembly of composite part + """ + assembly_subcomps = get_subcomponents(ac) + assembly_ids = {str(sc.instance_of) for sc in assembly_subcomps} + + assembled_subcomps = get_subcomponents(composite_part) + assembled_ids = {str(sc.instance_of) for sc in assembled_subcomps} + + has_composite = composite_part.identity in assembly_ids + + if has_composite: + for assembly_subcomponent in assembly_subcomps: + if str(assembly_subcomponent.instance_of) == composite_part.identity: + composite_subid = assembly_subcomponent.identity + + unassembled = assembled_ids.difference(assembly_ids) + + assembled_subids = {sc.identity for sc in assembly_subcomps if str(sc.instance_of) in assembled_ids} + + # TODO: Change sbol3.SBOL_CONTAINS to tyto call after resolution of + contained_map = {str(co.object) : str(co.subject) for co in ac.constraints if co.restriction == sbol3.SBOL_CONTAINS} + + uncontained = assembled_subids.difference(contained_map.keys()) + if composite_subid not in contained_map.keys(): + uncontained.add(composite_subid) + + pib_subids = [contained_map[key] for key in contained_map.keys() + if key in assembled_subids or key == composite_subid] + else: + unassembled = assembled_ids.difference(assembly_ids) + + assembled_subids = {sc.identity for sc in assembly_subcomps if str(sc.instance_of) in assembled_ids} + + # TODO: Change sbol3.SBOL_CONTAINS to tyto call after resolution of + contained_map = {str(co.object) : str(co.subject) for co in ac.constraints if co.restriction == sbol3.SBOL_CONTAINS} + + uncontained = assembled_subids.difference(contained_map.keys()) + + pib_subids = [contained_map[key] for key in contained_map.keys() if key in assembled_subids] + + parts_in_backbones = [sc.instance_of.lookup() for sc in get_subcomponents_by_identity(ac, pib_subids)] + + invalid_parts_in_backbones = [pib for pib in parts_in_backbones if not validate_part_in_backbone(pib)] + + # ligations = [i for i in assembly_comps[0].interactions if tyto.SBO.conversion in i.types] + + # digestions = [i for i in assembly_comps[0].interactions if tyto.SBO.cleavage in i.types] + + return (len(unassembled) == 0 and len(uncontained) == 0 and has_composite + and len(invalid_parts_in_backbones) == 0) + + +def is_assembly(a: sbol3.Activity) -> bool: + """Check if Activity is an assembly + + :param plan: Activity being checked + :return: true if it has the expected types for an assembly + """ + return set(ASSEMBLY_TYPES).issubset(a.types) + + +def validate_assembly(a: sbol3.Activity, composite_part: sbol3.Component) -> bool: + """Check if Activity represents the assembly of a composite part + + :param plan: Activity being validated and Component for composite part + :return: true if it follows best practices for representing assembly of composite part + """ + # TODO: Change sbol3.SBOL_DESIGN to tyto call after resolution of + + assembly_comps = [a.document.find(u.entity) for u in a.usage if sbol3.SBOL_DESIGN in u.roles] + + is_assembly_comp_valid = True + for assembly_comp in assembly_comps: + if not validate_assembly_component(assembly_comp, composite_part): + is_assembly_comp_valid = False + + return len(assembly_comps) == 1 and is_assembly_comp_valid diff --git a/sbol_utilities/component.py b/sbol_utilities/component.py index 921941e9..7647a0be 100644 --- a/sbol_utilities/component.py +++ b/sbol_utilities/component.py @@ -8,6 +8,14 @@ from sbol_utilities.workarounds import get_parent +def get_subcomponents(c: sbol3.Component) -> List[sbol3.SubComponent]: + return [f for f in c.features if isinstance(f, sbol3.SubComponent)] + + +def get_subcomponents_by_identity(c: sbol3.Component, ids: List[str]) -> List[sbol3.SubComponent]: + return [sc for sc in get_subcomponents(c) if sc.identity in ids] + + # TODO: consider allowing return of LocalSubComponent and ExternallyDefined def contained_components(roots: Union[sbol3.TopLevel, Iterable[sbol3.TopLevel]]) -> Set[sbol3.Component]: """Find the set of all SBOL Components contained within the roots or their children. From c3fdc42f068c824165504b04802307cc527f0c43 Mon Sep 17 00:00:00 2001 From: nroehner Date: Tue, 22 Feb 2022 19:51:15 -0500 Subject: [PATCH 2/5] Added tests for validation of composite part assemblies. --- test/test_build_planning.py | 152 ++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 test/test_build_planning.py diff --git a/test/test_build_planning.py b/test/test_build_planning.py new file mode 100644 index 00000000..0926a3a1 --- /dev/null +++ b/test/test_build_planning.py @@ -0,0 +1,152 @@ +import unittest + +import sbol3 +import tyto +from typing import Optional + +from sbol_utilities.build_planning import validate_composite_part_assemblies, SBOL_ASSEMBLY_PLAN + + +class TestBuildPlanning(unittest.TestCase): + + def test_validate_composite_part_assemblies(self): + test_doc = sbol3.Document() + + sbol3.set_namespace('http://testBuildPlanning.org') + + assert validate_composite_part_assemblies(assemble_BBa_K093005(test_doc)) + + assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'BBa_E1010_UNASSEMBLED')) + assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'BBa_K093005_UNASSEMBLED')) + assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'BBa_E1010_UNCONTAINED')) + assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'BBa_E1010_NOT_INSERT')) + assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'pSB1C3_NOT_BACKBONE')) + assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'EXTRA_ASSEMBLY_COMPONENT')) + +def assemble_BBa_K093005(doc: sbol3.Document, failure_mode: Optional[str] = ''): + doc = sbol3.Document() + + sbol3.set_namespace('http://test_build_planning.org') + + # Create assembled parts BBa_B0034 and BBa_E1010 + + assembled_rbs = sbol3.Component('BBa_B0034', sbol3.SBO_DNA, roles=[tyto.SO.ribosome_entry_site]) + assembled_cds = sbol3.Component('BBa_E1010', sbol3.SBO_DNA, roles=[tyto.SO.CDS]) + + doc.add(assembled_rbs) + doc.add(assembled_cds) + + # Create composite part BBa_K093005 + + composite_part = sbol3.Component('BBa_K093005', sbol3.SBO_DNA, roles=[tyto.SO.engineered_region]) + + composite_part_sc1 = sbol3.SubComponent(assembled_rbs) + composite_part_sc2 = sbol3.SubComponent(assembled_cds) + + composite_part.features += [composite_part_sc1] + composite_part.features += [composite_part_sc2] + + doc.add(composite_part) + + # Create backbone pSB1C3 + + backbone = sbol3.Component('pSB1C3', [sbol3.SBO_DNA, tyto.SO.circular], + roles=[tyto.SO.plasmid_vector]) + + doc.add(backbone) + + # Create part in backbone for BBa_B0034 in pSB1C3 + + rbs_in_backbone = sbol3.Component('BBa_B0034_in_pSB1C3', [sbol3.SBO_DNA, tyto.SO.circular], + roles=[tyto.SO.plasmid_vector]) + + pib1_sc1 = sbol3.SubComponent(assembled_rbs, roles=[tyto.SO.engineered_insert]) + rbs_in_backbone.features += [pib1_sc1] + pib1_sc2 = sbol3.SubComponent(backbone) + rbs_in_backbone.features += [pib1_sc2] + + doc.add(rbs_in_backbone) + + # Create part in backbone for BBa_E1010 in pSB1C3 + + cds_in_backbone = sbol3.Component('BBa_E1010_in_pSB1C3', [sbol3.SBO_DNA, tyto.SO.circular], + roles=[tyto.SO.plasmid_vector]) + + if failure_mode != 'BBa_E1010_NOT_INSERT': + pib2_sc1 = sbol3.SubComponent(assembled_cds, roles=[tyto.SO.engineered_insert]) + cds_in_backbone.features += [pib2_sc1] + + if failure_mode != 'pSB1C3_NOT_BACKBONE': + pib2_sc2 = sbol3.SubComponent(backbone) + cds_in_backbone.features += [pib2_sc2] + + doc.add(cds_in_backbone) + + # Create part in backbone for BBa_K093005 in pSB1C3 + + gene_in_backbone = sbol3.Component('BBa_K093005_in_pSB1C3', [sbol3.SBO_DNA, tyto.SO.circular], + roles=[tyto.SO.plasmid_vector]) + + pib3_sc1 = sbol3.SubComponent(composite_part, roles=[tyto.SO.engineered_insert]) + gene_in_backbone.features += [pib3_sc1] + + pib3_sc2 = sbol3.SubComponent(backbone) + gene_in_backbone.features += [pib3_sc2] + + doc.add(gene_in_backbone) + + # Create component for assembly of BBa_K093005 + + assembly_comp = sbol3.Component('BBa_K093005_assembly', tyto.SBO.functional_entity) + + assembly_comp_sc1 = sbol3.SubComponent(assembled_rbs) + assembly_comp.features += [assembly_comp_sc1] + if failure_mode != 'BBa_E1010_UNASSEMBLED': + assembly_comp_sc2 = sbol3.SubComponent(assembled_cds) + assembly_comp.features += [assembly_comp_sc2] + if failure_mode != 'BBa_K093005_UNASSEMBLED': + assembly_comp_sc3 = sbol3.SubComponent(composite_part) + assembly_comp.features += [assembly_comp_sc3] + assembly_comp_sc4 = sbol3.SubComponent(rbs_in_backbone) + assembly_comp.features += [assembly_comp_sc4] + assembly_comp_sc5 = sbol3.SubComponent(cds_in_backbone) + assembly_comp.features += [assembly_comp_sc5] + assembly_comp_sc6 = sbol3.SubComponent(gene_in_backbone) + assembly_comp.features += [assembly_comp_sc6] + + pib_contains_rbs = sbol3.Constraint(sbol3.SBOL_CONTAINS, assembly_comp_sc4, assembly_comp_sc1) + assembly_comp.constraints += [pib_contains_rbs] + + if failure_mode != 'BBa_E1010_UNCONTAINED' and failure_mode != 'BBa_E1010_UNASSEMBLED': + pib_contains_cds = sbol3.Constraint(sbol3.SBOL_CONTAINS, assembly_comp_sc5, assembly_comp_sc2) + assembly_comp.constraints += [pib_contains_cds] + + if failure_mode != 'BBa_K093005_UNASSEMBLED': + pib_contains_gene = sbol3.Constraint(sbol3.SBOL_CONTAINS, assembly_comp_sc6, assembly_comp_sc3) + assembly_comp.constraints += [pib_contains_gene] + + doc.add(assembly_comp) + + # Create activity for assembly of BBa_K093005 + + assembly = sbol3.Activity('assemble_BBa_K093005', types=[sbol3.SBOL_DESIGN, SBOL_ASSEMBLY_PLAN]) + + assembly_usage = sbol3.Usage(assembly_comp.identity, roles=[sbol3.SBOL_DESIGN]) + assembly.usage += [assembly_usage] + + if failure_mode == 'EXTRA_ASSEMBLY_COMPONENT': + extra_assembly_comp = sbol3.Component('Extra_BBa_K093005_assembly', tyto.SBO.functional_entity) + + doc.add(extra_assembly_comp) + + extra_assembly_usage = sbol3.Usage(extra_assembly_comp.identity, roles=[sbol3.SBOL_DESIGN]) + assembly.usage += [extra_assembly_usage] + + doc.add(assembly) + + composite_part.generated_by += [assembly.identity] + + return composite_part + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 87d053fc843d16b8e00aa8dcdb73a8d33241dab7 Mon Sep 17 00:00:00 2001 From: nroehner Date: Wed, 23 Feb 2022 11:39:25 -0500 Subject: [PATCH 3/5] Added more comments to describe functions for validating an assembly plan. Also modified part in backbone validation to use is_plasmid helper function and check if component contains a single insert and a single backbone. --- sbol_utilities/build_planning.py | 103 ++++++++++++++----------------- 1 file changed, 48 insertions(+), 55 deletions(-) diff --git a/sbol_utilities/build_planning.py b/sbol_utilities/build_planning.py index 5ab7359b..8412efe1 100644 --- a/sbol_utilities/build_planning.py +++ b/sbol_utilities/build_planning.py @@ -2,8 +2,11 @@ import tyto from sbol_utilities.component import get_subcomponents, get_subcomponents_by_identity +from sbol_utilities.helper_functions import is_plasmid -# TODO: Change SBOL_ASSEMBLY_PLAN and sbol3.SBOL_DESIGN to tyto calls after resolution of +# TODO: Delete SBOL_ASSEMBLY_PLAN and change its references to tyto.SBOL3.assemblyPlan once tyto supports the +# Design-Build-Test-Learn portion of the SBOL3 ontology +# See issues https://github.com/SynBioDex/tyto/issues/56 and https://github.com/SynBioDex/sbol-owl3/issues/5 SBOL_ASSEMBLY_PLAN = 'http://sbols.org/v3#assemblyPlan' ASSEMBLY_TYPES = {sbol3.SBOL_DESIGN, SBOL_ASSEMBLY_PLAN} @@ -12,45 +15,29 @@ def validate_part_in_backbone(pib: sbol3.Component) -> bool: """Check if a Component represents a part in backbone :param plan: Component being validated - :return: true if its structure follows the best practices for representing a part in backbone + :return: true if it has SubComponents for one insert and one vector backbone """ subcomps = get_subcomponents(pib) - has_insert = False - has_backbone = False + comps = [sc.instance_of.lookup() for sc in subcomps] - i = 0 - while (not has_insert or not has_backbone) and i < len(subcomps): - part = subcomps[i].instance_of.lookup() - - if tyto.SO.engineered_insert in subcomps[i].roles or tyto.SO.engineered_insert in part.roles: - has_insert = True - elif is_backbone(part): - has_backbone = True - - i = i + 1 - - return has_insert and has_backbone - - -def is_backbone(b: sbol3.Component) -> bool: - """Check if Component is a backbone - - :param plan: Component being checked - :return: true if it has an expected role for a backbone - """ - for role in b.roles: - if role == tyto.SO.vector_replicon or tyto.SO.vector_replicon.is_ancestor_of(role): - return True + # Get Components for SubComponents of part in backbone that have engineered_insert as one of their roles + # (that is, a role of either the Component or its SubComponent instance) + inserts = [comps[i] for i in range(0, len(comps)) + if tyto.SO.engineered_insert in subcomps[i].roles or tyto.SO.engineered_insert in comps[i].roles] + + # Get Components for SubComponents of part in backbone that are plasmids according to their roles + # (that is, the roles of the Components) + backbones = [c for c in comps if is_plasmid(c)] - return False + return len(inserts) == 1 and len(backbones) == 1 def validate_composite_part_assemblies(c: sbol3.Component) -> bool: - """Check if a Component for a composite part has valid assemblies + """Check if a Component for a composite part has only valid assemblies :param plan: Component being validated - :return: true if its assemblies follows the best practices for representing a composite part + :return: true if all of its assemblies are valid (see validate_assembly) """ activities = [g.lookup() for g in c.generated_by] @@ -63,48 +50,53 @@ def validate_assembly_component(ac: sbol3.Component, composite_part: sbol3.Compo """Check if Component represents the assembly of a composite part :param plan: Component being validated and Component for composite part - :return: true if it follows best practices for representing assembly of composite part + :return: true if it has (1) SubComponents for the composite part and its assembled parts + (2) SubComponents for these parts in their backbones, and + (3) a contains Constraint for each part in backbone and its insert. """ + # Get identities of Components that are SubComponents of the assembly Component assembly_subcomps = get_subcomponents(ac) assembly_ids = {str(sc.instance_of) for sc in assembly_subcomps} + # Get identities of Components for assembled parts that are SubComponents of the composite part assembled_subcomps = get_subcomponents(composite_part) assembled_ids = {str(sc.instance_of) for sc in assembled_subcomps} + # Check whether composite part is SubComponent of the assembly Component has_composite = composite_part.identity in assembly_ids - if has_composite: - for assembly_subcomponent in assembly_subcomps: - if str(assembly_subcomponent.instance_of) == composite_part.identity: - composite_subid = assembly_subcomponent.identity + # Determine identities of Components for assembled parts that are not SubComponents of the assembly Component + unassembled = assembled_ids.difference(assembly_ids) - unassembled = assembled_ids.difference(assembly_ids) + # Get identities of SubComponents for composite part and assembled parts in the assembly Component + for assembly_subcomponent in assembly_subcomps: + if str(assembly_subcomponent.instance_of) == composite_part.identity: + composite_subid = assembly_subcomponent.identity - assembled_subids = {sc.identity for sc in assembly_subcomps if str(sc.instance_of) in assembled_ids} + assembled_subids = {sc.identity for sc in assembly_subcomps if str(sc.instance_of) in assembled_ids} - # TODO: Change sbol3.SBOL_CONTAINS to tyto call after resolution of - contained_map = {str(co.object) : str(co.subject) for co in ac.constraints if co.restriction == sbol3.SBOL_CONTAINS} + # Build map from object to subject SubComponent identities for all contains Constraints in the assembly Component + # TODO: Change sbol3.SBOL_CONTAINS to tyto.SBOL3.contains once tyto supports SBOL3 constraint restrictions + # See issues https://github.com/SynBioDex/tyto/issues/55 and https://github.com/SynBioDex/sbol-owl3/issues/4 + contained_map = {str(co.object) : str(co.subject) for co in ac.constraints if co.restriction == sbol3.SBOL_CONTAINS} - uncontained = assembled_subids.difference(contained_map.keys()) + # Determine identities of SubComponents for assembly parts that are not the object of a contains Constraint + uncontained = assembled_subids.difference(contained_map.keys()) + + # Add identity of SubComoponent for composite part to uncontained set if it is not the object of contains Constraint + if has_composite: if composite_subid not in contained_map.keys(): uncontained.add(composite_subid) - pib_subids = [contained_map[key] for key in contained_map.keys() - if key in assembled_subids or key == composite_subid] - else: - unassembled = assembled_ids.difference(assembly_ids) - - assembled_subids = {sc.identity for sc in assembly_subcomps if str(sc.instance_of) in assembled_ids} - # TODO: Change sbol3.SBOL_CONTAINS to tyto call after resolution of - contained_map = {str(co.object) : str(co.subject) for co in ac.constraints if co.restriction == sbol3.SBOL_CONTAINS} - - uncontained = assembled_subids.difference(contained_map.keys()) - - pib_subids = [contained_map[key] for key in contained_map.keys() if key in assembled_subids] + # Get identities of SubComponents for parts in backbones that contain an assembled part or composite part + pib_subids = [contained_map[key] for key in contained_map.keys() + if key in assembled_subids or (has_composite and key == composite_subid)] + # Get identities of Components for parts in backbones parts_in_backbones = [sc.instance_of.lookup() for sc in get_subcomponents_by_identity(ac, pib_subids)] + # Determine which part in backbone Components are invalid invalid_parts_in_backbones = [pib for pib in parts_in_backbones if not validate_part_in_backbone(pib)] # ligations = [i for i in assembly_comps[0].interactions if tyto.SBO.conversion in i.types] @@ -128,10 +120,11 @@ def validate_assembly(a: sbol3.Activity, composite_part: sbol3.Component) -> boo """Check if Activity represents the assembly of a composite part :param plan: Activity being validated and Component for composite part - :return: true if it follows best practices for representing assembly of composite part + :return: true if it uses a single valid assembly Component (see validate_assembly_component) """ - # TODO: Change sbol3.SBOL_DESIGN to tyto call after resolution of - + # TODO: Change sbol3.SBOL_Design to tyto.SBOL3.design once tyto supports the + # Design-Build-Test-Learn portion of the SBOL3 ontology + # See issues https://github.com/SynBioDex/tyto/issues/56 and https://github.com/SynBioDex/sbol-owl3/issues/5 assembly_comps = [a.document.find(u.entity) for u in a.usage if sbol3.SBOL_DESIGN in u.roles] is_assembly_comp_valid = True From 79c02796218c6efe2ab97c1070f4490c85d8461a Mon Sep 17 00:00:00 2001 From: nroehner Date: Wed, 23 Feb 2022 11:54:10 -0500 Subject: [PATCH 4/5] Added doc strings to functions for getting SubComponents of Component. --- sbol_utilities/component.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sbol_utilities/component.py b/sbol_utilities/component.py index c5dcdab5..cd78c86f 100644 --- a/sbol_utilities/component.py +++ b/sbol_utilities/component.py @@ -12,10 +12,20 @@ def get_subcomponents(c: sbol3.Component) -> List[sbol3.SubComponent]: + """Get all Features of Component that are SubComponents + + :param obj: Component to get Features from + :return: List of Component Features that are SubComponents + """ return [f for f in c.features if isinstance(f, sbol3.SubComponent)] def get_subcomponents_by_identity(c: sbol3.Component, ids: List[str]) -> List[sbol3.SubComponent]: + """Get all SubComponents of Component that are instances of Components identified in ids + + :param obj: Component to get Subcomponents from + :return: List of SubComponents that are instances of Components identified in ids + """ return [sc for sc in get_subcomponents(c) if sc.identity in ids] From 5735eaedebba4824682a25fe6e87cf234a7bc68e Mon Sep 17 00:00:00 2001 From: nroehner Date: Wed, 23 Feb 2022 12:28:41 -0500 Subject: [PATCH 5/5] Added doc string and comments for test_build_planning. --- test/test_build_planning.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/test_build_planning.py b/test/test_build_planning.py index 0926a3a1..35af3fcb 100644 --- a/test/test_build_planning.py +++ b/test/test_build_planning.py @@ -10,17 +10,34 @@ class TestBuildPlanning(unittest.TestCase): def test_validate_composite_part_assemblies(self): + """Test function for validating composite part assemblies""" test_doc = sbol3.Document() sbol3.set_namespace('http://testBuildPlanning.org') + # Should work since composite part BBa_K093005 has an assembly Activity that uses an assembly Component + # that has SubComponents for BBa_K093005 and its assembled parts BBa_B0034 and BBa_E1010 and their parts in + # backbones. The assembly Component also has a contains Constraint with each part in backbone as a subject + # and the part insert as an object. assert validate_composite_part_assemblies(assemble_BBa_K093005(test_doc)) + # Should not work since the assembly Component is missing a SubComponent for the assembled part BBa_E1010 assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'BBa_E1010_UNASSEMBLED')) + + # Should not work since the assembly Component is missing a SubComponent for the composite part BBa_K093005 assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'BBa_K093005_UNASSEMBLED')) + + # Should not work since the assembly Component has no Constraint with BBa_E1010 as its object and its part in + # backbone as its subject assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'BBa_E1010_UNCONTAINED')) + + # Should not work since the part in backbone Component for BBa_E1010 is missing its insert SubComponent assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'BBa_E1010_NOT_INSERT')) + + # Should not work since the part in backbone Component for BBa_E1010 is missing its backbone SubComponent assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'pSB1C3_NOT_BACKBONE')) + + # Should not work since the assembly Activity uses more than one assembly Component assert not validate_composite_part_assemblies(assemble_BBa_K093005(test_doc, 'EXTRA_ASSEMBLY_COMPONENT')) def assemble_BBa_K093005(doc: sbol3.Document, failure_mode: Optional[str] = ''):