Skip to content

Commit

Permalink
Merge pull request #339 from tcmitchell/337-doc-load-default-namespace
Browse files Browse the repository at this point in the history
Fix document reading with default namespace
  • Loading branch information
tcmitchell authored Oct 25, 2021
2 parents 1d5ac15 + 87bca55 commit 6fedab9
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 27 deletions.
37 changes: 19 additions & 18 deletions sbol3/toplevel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import math
import posixpath
import uuid
from typing import List, Dict, Callable, Union
from urllib.parse import urlparse

Expand Down Expand Up @@ -49,25 +50,25 @@ def default_namespace(namespace: Union[None, str], identity: str) -> str:
# short circuit if a namespace is set
if namespace is not None:
return namespace
# Try using the default namespace if set
namespace = get_namespace()
if namespace is None:
# No default namespace was defined
# Try parsing the identity as a URL
parsed = urlparse(identity)
if parsed.scheme and parsed.netloc and parsed.path:
# This is a URL
# Reverse index to drop the displayId and use the rest
# for the namespace
delim = posixpath.sep
if '#' in identity:
delim = '#'
namespace = identity[:identity.rindex(delim)]
if namespace is None:
# TODO: what should we do here? We haven't been able to determine
# a namespace. But in the case where we're loading a file
# that's probably ok. We don't want that loading to fail.
default_namespace = get_namespace()
# If identity is a uuid, don't bother with namespaces
try:
# If it is a UUID, accept it as the identity
uuid.UUID(identity)
return default_namespace or PYSBOL3_DEFAULT_NAMESPACE
except ValueError:
pass
# If default namespace is a prefix of identity, use it for the namespace
if default_namespace and identity.startswith(default_namespace):
return default_namespace
# Identity does not start with the default namespace then
# heuristically determine the namespace. We use a greedy
# algorithm by assuming there is no local path, and that
# everything other than the display_id is the namespace.
delim = posixpath.sep
if '#' in identity:
delim = '#'
namespace = identity[:identity.rindex(delim)]
return namespace

def validate_identity(self, report: ValidationReport) -> None:
Expand Down
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@
install_requires=[
# Require at least rdflib 6.0.1, and allow newer versions
# of rdflib 6.x
'rdflib>=6.0.1,==6.*',
'python-dateutil~=2.8',
'pyshacl~=0.17.0',
'rdflib>=6.0.2,==6.*',
'python-dateutil~=2.8.2',
'pyshacl~=0.17.1',
],
test_suite='test',
tests_require=[
'pycodestyle~=2.7.0'
'pycodestyle~=2.8.0'
])
11 changes: 6 additions & 5 deletions test/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ def test_namespace_none(self):
# Test the exception case when a namespace cannot be deduced
identity = uuid.uuid4().urn
collection = sbol3.Collection(identity)
# The namespace should be None in this case.
# We can't determine a namespace from a UUID and we don't
# want to error and break file loading if we can't
# determine a namespace.
self.assertIsNone(collection.namespace)
# The namespace should always be set per SBOL 3.0.1
# "A TopLevel object MUST have precisely one hasNamespace property"
self.assertIsNotNone(collection.namespace)
# There should be no validation errors on this object
report = collection.validate()
self.assertEqual(0, len(report))


class TestExperiment(unittest.TestCase):
Expand Down
26 changes: 26 additions & 0 deletions test/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,32 @@ def test_json_ld_parser_bug(self):
self.assertEqual(m_turtle.source, m_json_ld.source)
self.assertEqual(m_turtle.namespace, m_json_ld.namespace)

def test_read_with_default_namespace(self):
# Test reading a file when the default namespace is set
# See https://github.com/SynBioDex/pySBOL3/issues/337
sbol3.set_namespace('http://example.com')
test_path = os.path.join(SBOL3_LOCATION, 'toggle_switch',
'toggle_switch.ttl')
doc = sbol3.Document()
doc.read(test_path)

def test_read_default_namespace(self):
# This is a modified version of the initial bug report for
# https://github.com/SynBioDex/pySBOL3/issues/337
doc = sbol3.Document()
sbol3.set_namespace('http://foo.org')
doc.add(sbol3.Sequence('bar'))
self.assertEqual(0, len(doc.validate()))
file_format = sbol3.SORTED_NTRIPLES
data = doc.write_string(file_format=file_format)

doc2 = sbol3.Document()
doc2.read_string(data, file_format=file_format) # Successful read

sbol3.set_namespace('http://baz.com/')
doc3 = sbol3.Document()
doc3.read_string(data, file_format=file_format)


if __name__ == '__main__':
unittest.main()
23 changes: 23 additions & 0 deletions test/test_toplevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,29 @@ def test_namespace_mismatch(self):
# Expecting at least one error
self.assertEqual(len(report), 0)

def test_namespace_mismatch_uuid(self):
# Now check a UUID with no default namespace set
# sbol3.set_namespace(None)
self.assertIsNone(sbol3.get_namespace())
c = sbol3.Component(uuid.uuid4().urn, types=[sbol3.SBO_DNA])
report = c.validate()
self.assertIsNotNone(report)
# Expecting at least one error
self.assertEqual(0, len(report))

def test_default_namespace_with_local_path(self):
# Make sure default namespace is honored when the identity has
# a local path included
test_namespace = 'https://github.com/synbiodex'
sbol3.set_namespace(test_namespace)
self.assertEqual(test_namespace, sbol3.get_namespace())
identity = posixpath.join(sbol3.get_namespace(), 'pysbol3', 'foo')
c = sbol3.Component(identity, types=[sbol3.SBO_DNA])
self.assertEqual(sbol3.get_namespace(), c.namespace)
self.assertEqual('foo', c.display_id)
self.assertEqual(identity, c.identity)
self.assertEqual(0, len(c.validate()))

def test_creation_namespace_mismatch(self):
# Prevent an identity/namespace mismatch on object creation
# See https://github.com/SynBioDex/pySBOL3/issues/277
Expand Down

0 comments on commit 6fedab9

Please sign in to comment.