Skip to content

Commit

Permalink
Merge pull request #129 from SynBioDex/123-copy_methods
Browse files Browse the repository at this point in the history
123 copy methods
  • Loading branch information
bbartley authored Dec 9, 2020
2 parents b415e99 + 822932f commit a43ab96
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 3 deletions.
8 changes: 8 additions & 0 deletions sbol3/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

class Component(TopLevel):

@staticmethod
def build_component(name: str, *, type_uri: str = SBOL_COMPONENT) -> SBOLObject:
missing = PYSBOL3_MISSING
obj = Component(name, [missing], type_uri=type_uri)
# Remove the dummy values
obj._properties[SBOL_TYPE] = []
return obj

def __init__(self, name: str, component_type: Union[List[str], str],
*, type_uri: str = SBOL_COMPONENT):
super().__init__(name, type_uri)
Expand Down
4 changes: 2 additions & 2 deletions sbol3/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from typing import Dict, Callable, List, Optional

import rdflib

# Get the rdflib-jsonld capability initialized
# Note: this is for side effect. The parser is not used.
# The side effect is that the JSON-LD parser is registered in RDFlib.
from rdflib_jsonld import parser as jsonld_parser

from . import *
from .object import BUILDER_REGISTER

_default_bindings = {
'sbol': SBOL3_NS,
Expand All @@ -34,7 +34,7 @@ def register_builder(type_uri: str,

# Map type URIs to a builder function to construct entities from
# RDF triples.
_uri_type_map: Dict[str, Callable[[str, str], SBOLObject]] = {}
_uri_type_map: Dict[str, Callable[[str, str], SBOLObject]] = BUILDER_REGISTER

def __init__(self):
self.logger = logging.getLogger(SBOL_LOGGER_NAME)
Expand Down
86 changes: 85 additions & 1 deletion sbol3/object.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import posixpath
import uuid
from collections import defaultdict
from typing import Optional
from urllib.parse import urlparse
from typing import Dict, Callable, Optional

from . import *

Expand Down Expand Up @@ -99,3 +99,87 @@ def find(self, search_string: str) -> Optional['SBOLObject']:
if result is not None:
return result
return None

def copy(self, target_doc=None, target_namespace=None):

new_uri = self.identity

# If caller specified a target_namespace argument, then copy the object into this
# new namespace.
if target_namespace:

# Map the identity of self into the target namespace
if hasattr(self, 'identity'):
old_uri = self.identity
new_uri = replace_namespace(old_uri, target_namespace, self.getTypeURI())

new_obj = BUILDER_REGISTER[self.type_uri](name=new_uri, type_uri=self.type_uri)

# Copy properties
for property_uri, value_store in self._properties.items():
new_obj._properties[property_uri] = value_store.copy()

# TODO:
# Map into new namespace

# Assign the new object to the target Document
if target_doc:
try:
target_doc.add(new_obj)
except TypeError:
pass # object is not TopLevel

# When an object is simply being cloned, the value of wasDerivedFrom should be
# copied exactly as is from self. However, when copy is being used to generate
# a new entity, the wasDerivedFrom should point back to self.
if self.identity == new_obj.identity:
new_obj.derived_from = self.derived_from
else:
new_obj.derived_from = self.identity

# Copy child objects recursively
for property_uri, object_list in self._owned_objects.items():
for o in object_list:
o_copy = o.copy(target_doc, target_namespace)
new_obj._owned_objects[property_uri].append(o_copy)
o_copy.parent = self

return new_obj


def replace_namespace(old_uri, target_namespace, rdf_type):
"""
Utility function for mapping an SBOL object's identity into a new namespace. The
rdf_type is used to map to and from sbol-typed namespaces.
"""

# Work around an issue where the Document itself is being copied and
# doesn't have its own URI, so old_uri is None. Return empty string
# because the identity is not allowed to be None.
if old_uri is None:
return ''

# If the value is an SBOL-typed URI, replace both the namespace and class name
class_name = parseClassName(rdf_type)
replacement_target = target_namespace + '/' + class_name

# If not an sbol typed URI, then just replace the namespace
if replacement_target not in old_uri:
replacement_target = target_namespace

if Config.getOption(ConfigOptions.SBOL_TYPED_URIS):
# Map into a typed namespace
replacement = getHomespace() + '/' + class_name
else:
# Map into a non-typed namespace
replacement = getHomespace()

new_uri = old_uri.replace(replacement_target, replacement)
if type(old_uri) is URIRef:
return URIRef(new_uri)
return new_uri


# Global store for builder methods. Custom SBOL classes
# register their builders in this store
BUILDER_REGISTER: Dict[str, Callable[[str, str], SBOLObject]] = {}
25 changes: 25 additions & 0 deletions test/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,31 @@ def test_trailing_slash(self):
identity = slash_identity.strip(posixpath.sep)
self.assertEqual(identity, c.identity)

def test_copy_properties(self):
doc = sbol3.Document()
root = sbol3.Component('root', sbol3.SBO_DNA)
root.name = 'foo'
root_copy = root.copy()
self.assertEqual(root_copy.name, 'foo')

def test_copy_child_objects(self):
doc = sbol3.Document()
root = sbol3.Component('root', sbol3.SBO_DNA)
sub1 = sbol3.Component('sub1', sbol3.SBO_DNA)
sub2 = sbol3.Component('sub2', sbol3.SBO_DNA)
sc1 = sbol3.SubComponent(sub1)
sc2 = sbol3.SubComponent(sub2)
root.features.append(sc1)
root.features.append(sc2)
doc.add(root)
doc.add(sub1)
doc.add(sub2)

doc2 = sbol3.Document()
root_copy = root.copy(target_doc=doc2)
self.assertEqual([sc.identity for sc in root.features],
[sc.identity for sc in root_copy.features])


if __name__ == '__main__':
unittest.main()

0 comments on commit a43ab96

Please sign in to comment.