Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 95 additions & 34 deletions sbol_utilities/sbol3_sbol2_conversion.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import tempfile
from pathlib import Path

import sbol3
import sbol2
from sbol2 import mapsto, model, sequenceconstraint
Expand Down Expand Up @@ -142,38 +145,58 @@ def visit_combinatorial_derivation(self, a: sbol3.CombinatorialDerivation):
# Priority: 2
raise NotImplementedError('Conversion of CombinatorialDerivation from SBOL3 to SBOL2 not yet implemented')

def visit_component(self, cp3: sbol3.Component):
def visit_component(self, comp3: sbol3.Component):
# Remap type if it's one of the ones that needs remapping; otherwise pass through unchanged
type_map = {sbol3.SBO_DNA: sbol2.BIOPAX_DNA, # TODO: distinguish BioPAX Dna from DnaRegion
sbol3.SBO_RNA: sbol2.BIOPAX_RNA, # TODO: distinguish BioPAX Rna from RnaRegion
sbol3.SBO_PROTEIN: sbol2.BIOPAX_PROTEIN,
sbol3.SBO_SIMPLE_CHEMICAL: sbol2.BIOPAX_SMALL_MOLECULE,
sbol3.SBO_NON_COVALENT_COMPLEX: sbol2.BIOPAX_COMPLEX}
types2 = [type_map.get(t, t) for t in cp3.types]
types2 = [type_map.get(t, t) for t in comp3.types]
# Make the Component object and add it to the document
cp2 = sbol2.ComponentDefinition(cp3.identity, types2, version=self._sbol2_version(cp3))
self.doc2.addComponentDefinition(cp2)
comp_def2 = sbol2.ComponentDefinition(comp3.identity, types2, version=self._sbol2_version(comp3))
self.doc2.addComponentDefinition(comp_def2)
# Convert the Component properties not covered by the constructor
cp2.roles = cp3.roles
cp2.sequences = cp3.sequences
if cp3.features:
raise NotImplementedError('Conversion of Component features from SBOL3 to SBOL2 not yet implemented')
if cp3.interactions:
raise NotImplementedError('Conversion of Component interactions from SBOL3 to SBOL2 not yet implemented')
if cp3.constraints:
raise NotImplementedError('Conversion of Component constraints from SBOL3 to SBOL2 not yet implemented')
if cp3.interface:
comp_def2.roles = comp3.roles
comp_def2.sequences = comp3.sequences
if comp3.features:
for feature in comp3.features:
if type(feature) == sbol3.subcomponent.SubComponent:
self.visit_sub_component(feature, comp_def2)
elif type(feature) == sbol3.compref.ComponentReference:
try:
self.visit_component_reference(feature)
except NotImplementedError as e:
# highlights the error message in red.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe that you should be swallowing the error here and turning it into a print statement.

print(f"\033[91m{e}\033[0m")
else:
raise NotImplementedError(
'Conversion of Component features from SBOL3 to SBOL2 not yet implemented')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Conversion of Component features from SBOL3 to SBOL2 not yet implemented')
f'Conversion of Component feature of type {type(feature)} from SBOL3 to SBOL2 not yet implemented')

if comp3.interactions:
for interaction in comp3.interactions:
try:
self.visit_interaction(interaction)
except NotImplementedError as e:
print(f"\033[91m{e}\033[0m")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - do not swallow errors here, or below

if comp3.constraints:
for constraint in comp3.constraints:
try:
pass
self.visit_constraint(constraint)
except NotImplementedError as e:
print(f"\033[91m{e}\033[0m")
if comp3.interface:
raise NotImplementedError('Conversion of Component interface from SBOL3 to SBOL2 not yet implemented')
if cp3.models:
if comp3.models:
raise NotImplementedError('Conversion of Component models from SBOL3 to SBOL2 not yet implemented')
# Map over all other TopLevel properties and extensions not covered by the constructor
self._convert_toplevel(cp3, cp2)
self._convert_toplevel(comp3, comp_def2)

def visit_component_reference(self, a: sbol3.ComponentReference):
def visit_component_reference(self, comp_ref3: sbol3.ComponentReference):
# Priority: 3
raise NotImplementedError('Conversion of ComponentReference from SBOL3 to SBOL2 not yet implemented')

def visit_constraint(self, a: sbol3.Constraint):
def visit_constraint(self, constraint: sbol3.Constraint):
# Priority: 2
raise NotImplementedError('Conversion of Constraint from SBOL3 to SBOL2 not yet implemented')

Expand Down Expand Up @@ -262,17 +285,26 @@ def visit_sequence(self, seq3: sbol3.Sequence):
# Map over all other TopLevel properties and extensions not covered by the constructor
self._convert_toplevel(seq3, seq2)

def visit_sequence_feature(self, a: sbol3.SequenceFeature):
def visit_sequence_feature(self, feat3: sbol3.SequenceFeature):
# Priority: 1
raise NotImplementedError('Conversion of SequenceFeature from SBOL3 to SBOL2 not yet implemented')

def visit_singular_unit(self, a: sbol3.SingularUnit):
# Priority: 4
raise NotImplementedError('Conversion of SingularUnit from SBOL3 to SBOL2 not yet implemented')

def visit_sub_component(self, a: sbol3.SubComponent):
def visit_sub_component(self, sub3: sbol3.SubComponent,
comp_def2: sbol2.ComponentDefinition):
# Priority: 1
raise NotImplementedError('Conversion of SubComponent from SBOL3 to SBOL2 not yet implemented')
# Make the Component, Module, or Functional_Component objects and add them to the document
# TODO Handle converting sub_components into Modules and FunctionalEntities when necessary
comp2 = sbol2.Component(sub3.identity)
comp2.roles = sub3.roles
comp2.roleIntegration = sub3.role_integration
comp2.sourceLocations = sub3.source_locations
comp2.definition = sub3.instance_of
comp2.displayId = sub3.display_id
comp_def2.components.add(comp2)

def visit_unit_division(self, a: sbol3.UnitDivision):
# Priority: 4
Expand Down Expand Up @@ -395,7 +427,7 @@ def visit_combinatorial_derivation(self, a: sbol2.CombinatorialDerivation):
# Priority: 2
raise NotImplementedError('Conversion of CombinatorialDerivation from SBOL2 to SBOL3 not yet implemented')

def visit_component_definition(self, cd2: sbol2.ComponentDefinition):
def visit_component_definition(self, comp_def2: sbol2.ComponentDefinition, sub3_comp2_equivalencies=None):
# Remap type if it's one of the ones that needs remapping; otherwise pass through unchanged
type_map = {sbol2.BIOPAX_DNA: sbol3.SBO_DNA,
'http://www.biopax.org/release/biopax-level3.owl#Dna': sbol3.SBO_DNA, # TODO: make reversible
Expand All @@ -404,27 +436,56 @@ def visit_component_definition(self, cd2: sbol2.ComponentDefinition):
sbol2.BIOPAX_PROTEIN: sbol3.SBO_PROTEIN,
sbol2.BIOPAX_SMALL_MOLECULE: sbol3.SBO_SIMPLE_CHEMICAL,
sbol2.BIOPAX_COMPLEX: sbol3.SBO_NON_COVALENT_COMPLEX}
types3 = [type_map.get(t, t) for t in cd2.types]
types3 = [type_map.get(t, t) for t in comp_def2.types]
# Make the Component object and add it to the document
cp3 = sbol3.Component(cd2.identity, types3, namespace=self._sbol3_namespace(cd2),
roles=cd2.roles, sequences=cd2.sequences)
self.doc3.add(cp3)
comp3 = sbol3.Component(comp_def2.identity, types3, namespace=self._sbol3_namespace(comp_def2),
roles=comp_def2.roles, sequences=comp_def2.sequences)
self.doc3.add(comp3)

# Convert the Component properties not covered by the constructor
if cd2.components:
raise NotImplementedError('Conversion of ComponentDefinition components '
'from SBOL2 to SBOL3 not yet implemented')
if cd2.sequenceAnnotations:
identity_mappings = {}
if comp_def2.components:
for comp2 in comp_def2.components:
self.visit_component(comp2, comp3, identity_mappings)

if comp_def2.sequenceAnnotations:
raise NotImplementedError('Conversion of ComponentDefinition sequenceAnnotations '
'from SBOL2 to SBOL3 not yet implemented')
if cd2.sequenceConstraints:
if comp_def2.sequenceConstraints:
raise NotImplementedError('Conversion of ComponentDefinition sequenceConstraints '
'from SBOL2 to SBOL3 not yet implemented')
# Map over all other TopLevel properties and extensions not covered by the constructor
self._convert_toplevel(cd2, cp3)
self._convert_toplevel(comp_def2, comp3)
self.handle_subcomponent_identity_triple_surgery(identity_mappings)

def visit_component(self, a: sbol2.Component):
def visit_component(self, comp2: sbol2.Component, comp3: sbol3.Component, identity_mappings):
# Priority: 2
raise NotImplementedError('Conversion of Component from SBOL2 to SBOL3 not yet implemented')
sub3 = sbol3.SubComponent(comp2.identity)
sub3.roles = comp2.roles
if comp2.roleIntegration:
sub3.role_integration = comp2.roleIntegration
if comp2.sourceLocations:
sub3.source_locations = comp2.sourceLocations
sub3.instance_of = comp2.definition
comp3.features += [sub3]
identity_mappings[sub3.identity] = comp2.identity

def handle_subcomponent_identity_triple_surgery(self, identity_mappings):
with tempfile.TemporaryDirectory() as tmpdir:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that you should be reading and writing tempfiles here. This could result in large numbers of complete read-write cycles in the course of a single conversion and be extremely slow.

I would suggesting saving up all of the identity surgery plans into a single shared datastructure, then doing the surgery on the graph like it's done over here:

def convert_identities2to3(sbol3_data: str) -> str:

temporary_file = Path(tmpdir) / 'temporary_file.nt'
self.doc3.write(temporary_file)
with open(temporary_file, 'r+') as file:

triples = file.readlines()
for index, triple in enumerate(triples):
for old_identity, new_identity in identity_mappings.items():
if f"<{old_identity}> <http://sbols.org/v3#instanceOf>" in triple:
triples[index] = triple.replace(old_identity, new_identity)

file.seek(0)
file.writelines(triples)
file.truncate()
self.doc3.read(temporary_file)

def visit_cut(self, a: sbol2.Cut):
# Priority: 2
Expand Down Expand Up @@ -504,7 +565,7 @@ def visit_module(self, a: sbol2.Module):
# Priority: 3
raise NotImplementedError('Conversion of Module from SBOL2 to SBOL3 not yet implemented')

def visit_module_definition(self, a: sbol2.ModuleDefinition):
def visit_module_definition(self, md2: sbol2.ModuleDefinition):
# Priority: 3
raise NotImplementedError('Conversion of ModuleDefinition from SBOL2 to SBOL3 not yet implemented')

Expand Down
Loading