Skip to content

Commit 6872614

Browse files
authored
Merge pull request #333 from tcmitchell/311-doc-add-list
Add collections of TopLevel via Document.add()
2 parents 558d531 + 428ed8a commit 6872614

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

sbol3/document.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import os
44
from typing import Dict, Callable, List, Optional, Any, Union
55

6+
# import typing for typing.Sequence, which we don't want to confuse
7+
# with sbol3.Sequence
8+
import typing
9+
610
import pyshacl
711
import rdflib
812

@@ -31,7 +35,7 @@ class Document:
3135

3236
@staticmethod
3337
def register_builder(type_uri: str,
34-
builder: Callable[[str, str], SBOLObject]) -> None:
38+
builder: Callable[[str, str], Identified]) -> None:
3539
"""A builder function will be called with an identity and a
3640
keyword argument type_uri.
3741
@@ -104,7 +108,6 @@ def _build_object(self, identity: str, types: List[str]) -> Optional[Identified]
104108
SBOL_TOP_LEVEL: CustomTopLevel
105109
}
106110
sbol_type = sbol_types[0]
107-
result = None
108111
if sbol_type in extension_types:
109112
# Build an extension object
110113
types.remove(sbol_type)
@@ -260,7 +263,7 @@ def read_string(self, data: str, file_format: str) -> None:
260263
graph.parse(data=data, format=file_format)
261264
return self._parse_graph(graph)
262265

263-
def add(self, obj: TopLevel) -> None:
266+
def _add(self, obj: TopLevel) -> TopLevel:
264267
"""Add objects to the document.
265268
"""
266269
if not isinstance(obj, TopLevel):
@@ -278,6 +281,43 @@ def add(self, obj: TopLevel) -> None:
278281
def assign_document(x: Identified):
279282
x.document = self
280283
obj.traverse(assign_document)
284+
return obj
285+
286+
def _add_all(self, objects: typing.Sequence[TopLevel]) -> typing.Sequence[TopLevel]:
287+
# Perform type check of all objects.
288+
# We do this to avoid finding out part way through that an
289+
# object can't be added. That would leave the document in an
290+
# unknown state.
291+
for obj in objects:
292+
if not isinstance(obj, TopLevel):
293+
if isinstance(obj, Identified):
294+
raise TypeError(f'{obj.identity} is not a TopLevel object')
295+
else:
296+
raise TypeError(f'{repr(obj)} is not a TopLevel object')
297+
298+
# Dispatch to Document._add to add the individual objects
299+
for obj in objects:
300+
self._add(obj)
301+
# return the passed argument
302+
return objects
303+
304+
def add(self, objects: Union[TopLevel, typing.Sequence[TopLevel]]) -> Union[TopLevel, typing.Sequence[TopLevel]]:
305+
# objects must be TopLevel or iterable. If neither, raise a TypeError.
306+
#
307+
# Note: Python documentation for collections.abc says "The only
308+
# reliable way to determine whether an object is iterable is to
309+
# call iter(obj)." `iter` will raise TypeError if the object is
310+
# not iterable
311+
if not isinstance(objects, TopLevel):
312+
try:
313+
iter(objects)
314+
except TypeError:
315+
raise TypeError('argument must be either TopLevel or Iterable')
316+
# Now dispatch to the appropriate method
317+
if isinstance(objects, TopLevel):
318+
return self._add(objects)
319+
else:
320+
return self._add_all(objects)
281321

282322
def _find_in_objects(self, search_string: str) -> Optional[Identified]:
283323
# TODO: implement recursive search

test/test_document.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ def test_add(self):
7575
with self.assertRaises(TypeError):
7676
doc.add(non_top_level)
7777
seq = sbol3.Sequence('seq1')
78-
doc.add(seq)
78+
added_seq = doc.add(seq)
79+
# Document.add should return the object
80+
# See https://github.com/SynBioDex/pySBOL3/issues/272
81+
self.assertEqual(seq, added_seq)
7982
seq2 = doc.find(seq.identity)
8083
self.assertEqual(seq.identity, seq2.identity)
8184

@@ -89,6 +92,24 @@ def test_add_multiple(self):
8992
with self.assertRaises(ValueError):
9093
document.add(experiment2)
9194

95+
def test_add_iterable(self):
96+
# See https://github.com/SynBioDex/pySBOL3/issues/311
97+
sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
98+
doc = sbol3.Document()
99+
objects = [sbol3.Component(name, types=[sbol3.SBO_DNA])
100+
for name in ['foo', 'bar', 'baz' 'quux']]
101+
result = doc.add(objects)
102+
self.assertEqual(len(objects), len(result))
103+
self.assertListEqual(objects, result)
104+
#
105+
# Test adding a non-TopLevel in a list
106+
doc = sbol3.Document()
107+
objects = [sbol3.Component(name, types=[sbol3.SBO_DNA])
108+
for name in ['foo', 'bar', 'baz' 'quux']]
109+
objects.insert(2, 'non-TopLevel')
110+
with self.assertRaises(TypeError):
111+
doc.add(objects)
112+
92113
def test_write(self):
93114
sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
94115
doc = sbol3.Document()

0 commit comments

Comments
 (0)