Skip to content

Commit 7e577e6

Browse files
committed
bp011->activity repr draft
1 parent 8ab606a commit 7e577e6

2 files changed

Lines changed: 145 additions & 142 deletions

File tree

src/buildcompiler/buildcompiler.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,9 @@ def _construct_plasmid_dict(
475475
plasmid_dict = {}
476476
for part in part_list:
477477
for plasmid in self.indexed_plasmids:
478-
if ENGINEERED_PLASMID in plasmid.plasmid_definition.roles:
478+
if (
479+
ENGINEERED_PLASMID in plasmid.plasmid_definition.roles
480+
): # TODO only grab implemented plasmids
479481
for component in plasmid.plasmid_definition.components:
480482
if (
481483
component.definition == str(part)

src/buildcompiler/sbol2build.py

Lines changed: 142 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from Bio.Seq import Seq
44
from pydna.dseqrecord import Dseqrecord
55
from itertools import product
6+
from .buildcompiler import Plasmid
67
from typing import List, Union, Tuple
78
from .constants import DNA_TYPES
89

@@ -11,6 +12,74 @@
1112
sbol2.Config.setOption(sbol2.ConfigOptions.SBOL_TYPED_URIS, False)
1213

1314

15+
class Assembly:
16+
"""Creates an Assembly Plan.
17+
18+
:param name: Name of the assembly plan ModuleDefinition.
19+
:param part_plasmids: Parts in backbone to be assembled.
20+
:param plasmid_acceptor_backbone: Backbone in which parts are inserted on the assembly.
21+
:param restriction_enzyme: Restriction enzyme name used by PyDNA. Case sensitive, follow standard restriction enzyme nomenclature, i.e. 'BsaI'
22+
:param document: SBOL Document where the assembly plan will be created.
23+
"""
24+
25+
def __init__( # TODO add fields for activity/agent/plan
26+
self,
27+
part_plasmids: List[Plasmid],
28+
backbone_plasmid: Plasmid,
29+
restriction_enzyme: sbol2.ComponentDefinition, # TODO search for implementation in document, or domesticate the RE
30+
ligase: sbol2.ComponentDefinition,
31+
document: sbol2.Document,
32+
):
33+
self.part_plasmids = part_plasmids
34+
self.backbone = backbone_plasmid
35+
# self.restriction_enzyme = rebase_restriction_enzyme(restriction_enzyme)
36+
self.extracted_parts = [] # list of tuples [ComponentDefinition, Sequence]
37+
self.document = document
38+
self.assembly_activity = sbol2.Activity("assembly")
39+
self.composites = []
40+
41+
def run(self) -> List[Tuple[sbol2.ComponentDefinition, sbol2.Sequence]]:
42+
"""Runs full assembly simulation.
43+
44+
`document` parameter of golden_gate_assembly_plan object is updated by reference to include assembly plan ModuleDefinition and all related information.
45+
46+
Runs :func:`part_digestion` for all `part_plasmids` and :func:`backbone_digestion` for `plasmid_acceptor_backbone` with `restriction_enzyme`. Then runs :func:`ligation` with these parts to form composites.
47+
48+
:return: List of all composites generated, in the form of tuples of ComponentDefinition and Sequence.
49+
"""
50+
for plasmid in self.part_plasmids:
51+
plasmid_impl = plasmid.plasmid_implementations[
52+
0
53+
] # TODO update with more sophisticated selection process?
54+
extracts_tuple_list, _ = part_digestion(
55+
plasmid_impl,
56+
[self.restriction_enzyme],
57+
self.assembly_activity,
58+
self.document,
59+
)
60+
# append_extracts_to_doc(extracts_tuple_list, self.document) #TODO remove this? extracted part definition should not be needed in new implementation. check to see if it's used in ligation
61+
self.extracted_parts.append(extracts_tuple_list[0][0])
62+
63+
backbone_impl = self.backbone.plasmid_implementations[0]
64+
extracts_tuple_list, _ = backbone_digestion(
65+
backbone_impl,
66+
[self.restriction_enzyme],
67+
self.assembly_activity,
68+
self.document,
69+
)
70+
71+
# append_extracts_to_doc(extracts_tuple_list, self.document)
72+
self.extracted_parts.append(extracts_tuple_list[0][0])
73+
74+
self.composites = ligation(
75+
self.extracted_parts, self.assembly_activity, self.document
76+
) # TODO pass ligase into ligation as implementation, implement logic to find in impl collection otherwise 'domesticate'
77+
78+
append_extracts_to_doc(self.composites, self.document)
79+
80+
return self.composites
81+
82+
1483
def rebase_restriction_enzyme(name: str, **kwargs) -> sbol2.ComponentDefinition:
1584
"""Creates an ComponentDefinition Restriction Enzyme Component from rebase.
1685
@@ -196,36 +265,29 @@ def is_circular(obj: sbol2.ComponentDefinition) -> bool:
196265

197266

198267
def part_digestion(
199-
reactant: Union[sbol2.ComponentDefinition, sbol2.ModuleDefinition],
268+
reactant: sbol2.Implementation,
200269
restriction_enzymes: List[sbol2.ComponentDefinition],
201-
assembly_plan: sbol2.ModuleDefinition,
270+
assembly_activity: sbol2.Activity,
202271
document: sbol2.Document,
203272
**kwargs,
204273
) -> Tuple[
205274
List[Tuple[sbol2.ComponentDefinition, sbol2.Sequence]], sbol2.ModuleDefinition
206275
]:
207276
"""Runs a simulated digestion on the top level sequence in the reactant ComponentDefinition or ModuleDefinition with the given restriciton enzymes, creating a extracted part ComponentDefinition, a digestion Interaction, and converts existing scars to 5' and 3' overhangs.
208-
The product ComponentDefinition is assumed the open backbone in this case.
277+
The product ComponentDefinition is assumed the digested part in this case.
209278
210279
Written for use with the SBOL2.3 output of https://sbolcanvas.org
211280
212-
:param reactant: DNA to be digested as SBOL ComponentDefinition or ModuleDefinition, usually a part_in_backbone. ComponentDefinition is the best-practice type for plasmids..
281+
:param reactant: Plasmid DNA to be digested as SBOL ComponentDefinition
213282
:param restriction_enzymes: Restriction enzymes as :class:`sbol2.ComponentDefinition`
214283
(generate with :func:`rebase_restriction_enzyme`).
215284
:param assembly_plan: SBOL ModuleDefinition to contain the functional components, interactions, and participations
216285
:param document: original SBOL2 document to be used to extract referenced objects.
217286
:return: A tuple of a list ComponentDefinitions and Sequences, and an assembly plan ModuleDefinition.
218287
"""
219-
if type(reactant) is sbol2.ModuleDefinition:
220-
# extract component definition from module
221-
reactant_displayId = reactant.functionalComponents[0].displayId
222-
reactant_def_URI = reactant.functionalComponents[0].definition
223-
reactant_component_definition = document.getComponentDefinition(
224-
reactant_def_URI
225-
)
226-
else:
227-
reactant_displayId = reactant.displayId
228-
reactant_component_definition = reactant
288+
289+
reactant_component_definition = document.get(reactant.built)
290+
reactant_displayId = reactant_component_definition.displayId
229291

230292
types = set(reactant_component_definition.types or [])
231293

@@ -237,33 +299,46 @@ def part_digestion(
237299
raise ValueError(
238300
f"The reactant needs to have precisely one sequence. The input reactant has {len(reactant.sequences)} sequences"
239301
)
240-
participations = []
302+
# participations = []
241303
extracts_list = []
242304
restriction_enzymes_pydna = []
243305

244306
for re in restriction_enzymes:
245307
enzyme = Restriction.__dict__[re.name]
246308
restriction_enzymes_pydna.append(enzyme)
247309

248-
enzyme_component = sbol2.FunctionalComponent(uri=f"{re.name}_enzyme")
249-
enzyme_component.definition = re
250-
enzyme_component.displayID = f"{re.name}_enzyme"
251-
enzyme_in_module = False
252-
253-
for comp in assembly_plan.functionalComponents:
254-
if comp.displayId == enzyme_component.displayID:
255-
enzyme_component = comp
256-
enzyme_in_module = True
257-
258-
if not enzyme_in_module:
259-
assembly_plan.functionalComponents.add(enzyme_component)
310+
# enzyme_component = sbol2.FunctionalComponent(uri=f"{re.name}_enzyme")
311+
# enzyme_component.definition = re
312+
# enzyme_component.displayID = f"{re.name}_enzyme"
313+
314+
enzyme_implmentation = sbol2.Implementation(
315+
uri=f"{re.name}_enzyme"
316+
) # TODO take implementation in as parameter, this should be done in Restriction Enzyme domestication
317+
enzyme_implmentation.built = re
318+
enzyme_in_activity = False
319+
320+
for usage in assembly_activity.usages:
321+
entity_URI = usage.entity
322+
entity = document.get(entity_URI)
323+
324+
if entity.displayId == re.displayID:
325+
enzyme_in_activity = True
326+
327+
if not enzyme_in_activity:
328+
assembly_activity.usages.add(
329+
sbol2.Usage(
330+
uri=f"{re.name}_enzyme",
331+
entity=enzyme_implmentation,
332+
role="http://sbols.org/v2#build",
333+
)
334+
)
260335

261-
modifier_participation = sbol2.Participation(uri="restriction")
262-
modifier_participation.participant = enzyme_component
263-
modifier_participation.roles = [
264-
"http://identifiers.org/biomodels.sbo/SBO:0000019"
265-
]
266-
participations.append(modifier_participation)
336+
# modifier_participation = sbol2.Participation(uri="restriction")
337+
# modifier_participation.participant = enzyme_component
338+
# modifier_participation.roles = [
339+
# "http://identifiers.org/biomodels.sbo/SBO:0000019"
340+
# ]
341+
# participations.append(modifier_participation)
267342

268343
# Inform topology to PyDNA, if not found assuming linear.
269344
if is_circular(reactant_component_definition):
@@ -284,9 +359,9 @@ def part_digestion(
284359
f"Not supported number of products. Found{len(digested_reactant)}"
285360
)
286361
elif circular and len(digested_reactant) == 2:
287-
part_extract, backbone = sorted(digested_reactant, key=len)
362+
part_extract, _ = sorted(digested_reactant, key=len)
288363
elif linear and len(digested_reactant) == 3:
289-
prefix, part_extract, suffix = digested_reactant
364+
_, part_extract, _ = digested_reactant
290365
else:
291366
raise ValueError(
292367
f"Reactant {reactant_component_definition.displayId} has no valid topology type, with {len(digested_reactant)} digested products, types: {reactant_component_definition.types}, and roles: {reactant_component_definition.roles}"
@@ -301,17 +376,19 @@ def part_digestion(
301376
)
302377
product_sequence = str(part_extract.seq)
303378
prod_component_definition, prod_seq = dna_componentdefinition_with_sequence(
304-
identity=f"{reactant.displayId if isinstance(reactant, sbol2.ComponentDefinition) else reactant.functionalComponents[0].displayId}_extracted_part",
379+
identity=f"{reactant_component_definition.displayId}_extracted_part",
305380
sequence=product_sequence,
306381
**kwargs,
307382
)
308383
prod_component_definition.wasDerivedFrom = reactant_component_definition.identity
309384
extracts_list.append((prod_component_definition, prod_seq))
310385

386+
# TODO explore how much granulatity in overhang representation is needed to preserve final composite annotations/components
387+
311388
# five prime overhang
312389
five_prime_oh_definition = sbol2.ComponentDefinition(
313390
uri=f"{reactant_displayId}_five_prime_oh"
314-
) # TODO: ensure circular type is preserved for sbh visualization
391+
)
315392
five_prime_oh_definition.addRole("http://identifiers.org/so/SO:0001932")
316393
five_prime_oh_location = sbol2.Range(
317394
uri="five_prime_oh_location", start=1, end=len(product_5_prime_ss_end)
@@ -360,7 +437,7 @@ def part_digestion(
360437

361438
original_part_def_URI = ""
362439

363-
# enccode ontologies of overhangs
440+
# enccode ontologies of overhangs (may no longer be necessary)
364441
for definition in document.componentDefinitions:
365442
for seqURI in definition.sequences:
366443
seq = document.getSequence(seqURI)
@@ -413,36 +490,36 @@ def part_digestion(
413490
prod_component_definition.addType("http://identifiers.org/so/SO:0000987") # linear
414491

415492
# Add reference to part in backbone
416-
reactant_component = sbol2.FunctionalComponent(uri=f"{reactant_displayId}_reactant")
417-
reactant_component.definition = reactant_component_definition
418-
assembly_plan.functionalComponents.add(reactant_component)
493+
# reactant_component = sbol2.FunctionalComponent(uri=f"{reactant_displayId}_reactant")
494+
# reactant_component.definition = reactant_component_definition
495+
# assembly_plan.functionalComponents.add(reactant_component)
419496

420497
# Create reactant Participation.
421-
reactant_participation = sbol2.Participation(uri=f"{reactant_displayId}_reactant")
422-
reactant_participation.participant = reactant_component
423-
reactant_participation.roles = [sbol2.SBO_REACTANT]
424-
participations.append(reactant_participation)
425-
426-
prod_component = sbol2.FunctionalComponent(
427-
uri=f"{reactant_displayId}_digestion_product"
428-
)
429-
prod_component.definition = prod_component_definition
430-
assembly_plan.functionalComponents.add(prod_component)
431-
432-
product_participation = sbol2.Participation(uri=f"{reactant_displayId}_product")
433-
product_participation.participant = prod_component
434-
product_participation.roles = [sbol2.SBO_PRODUCT]
435-
participations.append(product_participation)
498+
# reactant_participation = sbol2.Participation(uri=f"{reactant_displayId}_reactant")
499+
# reactant_participation.participant = reactant_component
500+
# reactant_participation.roles = [sbol2.SBO_REACTANT]
501+
# participations.append(reactant_participation)
502+
503+
# prod_component = sbol2.FunctionalComponent(
504+
# uri=f"{reactant_displayId}_digestion_product"
505+
# )
506+
# prod_component.definition = prod_component_definition
507+
# assembly_plan.functionalComponents.add(prod_component)
508+
509+
# product_participation = sbol2.Participation(uri=f"{reactant_displayId}_product")
510+
# product_participation.participant = prod_component
511+
# product_participation.roles = [sbol2.SBO_PRODUCT]
512+
# participations.append(product_participation)
436513

437514
# Make Interaction
438-
interaction = sbol2.Interaction(
439-
uri=f"{reactant_displayId}_digestion_interaction",
440-
interaction_type="http://identifiers.org/biomodels.sbo/SBO:0000178",
441-
)
442-
interaction.participations = participations
443-
assembly_plan.interactions.add(interaction)
515+
# interaction = sbol2.Interaction(
516+
# uri=f"{reactant_displayId}_digestion",
517+
# interaction_type="http://identifiers.org/biomodels.sbo/SBO:0000178",
518+
# )
519+
# interaction.participations = participations
520+
# assembly_plan.interactions.add(interaction)
444521

445-
return extracts_list, assembly_plan
522+
return extracts_list, assembly_activity
446523

447524

448525
def backbone_digestion(
@@ -996,7 +1073,8 @@ def ligation(
9961073

9971074
products_list.append([composite_component_definition, composite_seq])
9981075
composite_number += 1
999-
return products_list
1076+
1077+
return products_list # TODO instead of returning list of products CDs to append to doc, append all CDs and return list of their implementations
10001078

10011079

10021080
def append_extracts_to_doc(
@@ -1031,80 +1109,3 @@ def add_object_to_doc(
10311109
pass
10321110
else:
10331111
raise e
1034-
1035-
1036-
class golden_gate_assembly_plan:
1037-
"""Creates an Assembly Plan.
1038-
1039-
:param name: Name of the assembly plan ModuleDefinition.
1040-
:param parts_in_backbone: Parts in backbone to be assembled.
1041-
:param plasmid_acceptor_backbone: Backbone in which parts are inserted on the assembly.
1042-
:param restriction_enzyme: Restriction enzyme name used by PyDNA. Case sensitive, follow standard restriction enzyme nomenclature, i.e. 'BsaI'
1043-
:param document: SBOL Document where the assembly plan will be created.
1044-
"""
1045-
1046-
def __init__(
1047-
self,
1048-
name: str,
1049-
parts_in_backbone: List[sbol2.Document],
1050-
plasmid_acceptor_backbone: sbol2.Document,
1051-
restriction_enzyme: str,
1052-
document: sbol2.Document,
1053-
):
1054-
self.name = name
1055-
self.parts_in_backbone = parts_in_backbone
1056-
self.backbone = plasmid_acceptor_backbone
1057-
self.restriction_enzyme = rebase_restriction_enzyme(restriction_enzyme)
1058-
self.extracted_parts = [] # list of tuples [ComponentDefinition, Sequence]
1059-
self.document = document
1060-
1061-
self.assembly_plan = sbol2.ModuleDefinition(name)
1062-
self.document.add(self.assembly_plan)
1063-
self.document.add(self.restriction_enzyme)
1064-
self.composites = []
1065-
1066-
def run(
1067-
self, plasmids_in_module_definitions=False
1068-
) -> List[Tuple[sbol2.ComponentDefinition, sbol2.Sequence]]:
1069-
"""Runs full assembly simulation.
1070-
1071-
`document` parameter of golden_gate_assembly_plan object is updated by reference to include assembly plan ModuleDefinition and all related information.
1072-
1073-
Runs :func:`part_digestion` for all `parts_in_backbone` and :func:`backbone_digestion` for `plasmid_acceptor_backbone` with `restriction_enzyme`. Then runs :func:`ligation` with these parts to form composites.
1074-
1075-
:return: List of all composites generated, in the form of tuples of ComponentDefinition and Sequence.
1076-
"""
1077-
for part_doc in self.parts_in_backbone:
1078-
if plasmids_in_module_definitions:
1079-
topLevel = part_doc.getModuleDefinition(
1080-
"https://sbolcanvas.org/module1"
1081-
) # TODO change to toplevel or some other index?
1082-
else:
1083-
topLevel = part_doc.componentDefinitions[0]
1084-
extracts_tuple_list, _ = part_digestion(
1085-
topLevel, [self.restriction_enzyme], self.assembly_plan, part_doc
1086-
) # make sure assembly plan is pass-by-reference
1087-
1088-
append_extracts_to_doc(extracts_tuple_list, self.document)
1089-
self.extracted_parts.append(extracts_tuple_list[0][0])
1090-
1091-
if plasmids_in_module_definitions:
1092-
topLevel = self.backbone.getModuleDefinition(
1093-
"https://sbolcanvas.org/module1"
1094-
) # TODO change to toplevel or some other index?
1095-
else:
1096-
topLevel = self.backbone.componentDefinitions[0]
1097-
extracts_tuple_list, _ = backbone_digestion(
1098-
topLevel, [self.restriction_enzyme], self.assembly_plan, self.backbone
1099-
)
1100-
1101-
append_extracts_to_doc(extracts_tuple_list, self.document)
1102-
self.extracted_parts.append(extracts_tuple_list[0][0])
1103-
1104-
self.composites = ligation(
1105-
self.extracted_parts, self.assembly_plan, self.document
1106-
)
1107-
1108-
append_extracts_to_doc(self.composites, self.document)
1109-
1110-
return self.composites

0 commit comments

Comments
 (0)