aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/schema/test_schema.py271
-rw-r--r--test/schema/test_serialize.py1007
-rw-r--r--test/schema/test_types.py240
3 files changed, 1253 insertions, 265 deletions
diff --git a/test/schema/test_schema.py b/test/schema/test_schema.py
index 888cdca..1b45db0 100644
--- a/test/schema/test_schema.py
+++ b/test/schema/test_schema.py
@@ -10,7 +10,7 @@ import unittest
# bsfs imports
from bsfs.namespace import ns
-from bsfs.schema import types
+from bsfs.schema import types, from_string
from bsfs.utils import errors
# objects to test
@@ -55,7 +55,7 @@ class TestSchema(unittest.TestCase):
'''
# nodes
- self.n_root = types.Node(ns.bsfs.Node, None)
+ self.n_root = types.ROOT_NODE
self.n_ent = types.Node(ns.bsfs.Entity, types.Node(ns.bsfs.Node, None))
self.n_img = types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Entity, types.Node(ns.bsfs.Node, None)))
self.n_tag = types.Node(ns.bsfs.Tag, types.Node(ns.bsfs.Node, None))
@@ -63,24 +63,32 @@ class TestSchema(unittest.TestCase):
self.nodes = [self.n_root, self.n_ent, self.n_img, self.n_tag, self.n_unused]
# literals
- self.l_root = types.Literal(ns.bsfs.Literal, None)
+ self.l_root = types.ROOT_LITERAL
+ self.l_number = types.ROOT_NUMBER
self.l_string = types.Literal(ns.xsd.string, types.Literal(ns.bsfs.Literal, None))
- self.l_integer = types.Literal(ns.xsd.integer, types.Literal(ns.bsfs.Literal, None))
+ self.l_integer = types.Literal(ns.xsd.integer, self.l_number)
self.l_unused = types.Literal(ns.xsd.boolean, types.Literal(ns.bsfs.Literal, None))
- self.literals = [self.l_root, self.l_string, self.l_integer, self.l_unused]
+ self.literals = [self.l_root, self.l_number, self.l_string, self.l_integer, self.l_unused]
# predicates
- self.p_root = types.Predicate(ns.bsfs.Predicate, None, types.Node(ns.bsfs.Node, None), None, False)
+ self.p_root = types.ROOT_PREDICATE
+ self.f_root = types.ROOT_FEATURE
self.p_tag = self.p_root.get_child(ns.bse.tag, self.n_ent, self.n_tag, False)
self.p_group = self.p_tag.get_child(ns.bse.group, self.n_img, self.n_tag, False)
self.p_comment = self.p_root.get_child(ns.bse.comment, self.n_root, self.l_string, True)
- self.predicates = [self.p_root, self.p_tag, self.p_group, self.p_comment]
+ self.predicates = [self.p_root, self.f_root, self.p_tag, self.p_group, self.p_comment]
def test_construction(self):
+ # no args yields a minimal schema
+ schema = Schema()
+ self.assertSetEqual(set(schema.nodes()), {self.n_root})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_number})
+ self.assertSetEqual(set(schema.predicates()), {self.p_root, self.f_root})
+
# nodes and literals are optional
schema = Schema(self.predicates)
self.assertSetEqual(set(schema.nodes()), {self.n_root, self.n_ent, self.n_img, self.n_tag})
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number})
self.assertSetEqual(set(schema.predicates()), set(self.predicates))
# predicates, nodes, and literals are respected
@@ -101,21 +109,21 @@ class TestSchema(unittest.TestCase):
# literals are complete
schema = Schema(self.predicates, self.nodes, None)
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number})
schema = Schema(self.predicates, self.nodes, [])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number})
schema = Schema(self.predicates, self.nodes, [self.l_string])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number})
schema = Schema(self.predicates, self.nodes, [self.l_integer])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_integer})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_integer, self.l_number})
schema = Schema(self.predicates, self.nodes, [self.l_integer, self.l_unused])
self.assertSetEqual(set(schema.literals()), set(self.literals))
# predicates are complete
schema = Schema([], self.nodes, self.literals)
- self.assertSetEqual(set(schema.predicates()), set())
+ self.assertSetEqual(set(schema.predicates()), {self.p_root, self.f_root})
schema = Schema([self.p_group], self.nodes, self.literals)
- self.assertSetEqual(set(schema.predicates()), {self.p_root, self.p_tag, self.p_group})
+ self.assertSetEqual(set(schema.predicates()), {self.p_root, self.f_root, self.p_tag, self.p_group})
schema = Schema([self.p_group, self.p_comment], self.nodes, self.literals)
self.assertSetEqual(set(schema.predicates()), set(self.predicates))
@@ -153,21 +161,28 @@ class TestSchema(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, Schema,
{}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {}, {types.Node(ns.bsfs.Foo, None)})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {}, {types.Node(ns.bsfs.Foo, None)})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {types.Node(ns.bsfs.Foo, None)}, {})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {types.Node(ns.bsfs.Foo, None)}, {})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
+
def test_str(self):
+ # string conversion
self.assertEqual(str(Schema([])), 'Schema()')
self.assertEqual(str(Schema([], [], [])), 'Schema()')
self.assertEqual(str(Schema(self.predicates, self.nodes, self.literals)), 'Schema()')
- self.assertEqual(repr(Schema([])), 'Schema([], [], [])')
- self.assertEqual(repr(Schema([], [], [])), 'Schema([], [], [])')
+ # repr conversion with only default nodes, literals, and predicates
+ n = [ns.bsfs.Node]
+ l = [ns.bsfs.Literal, ns.bsfs.Number]
+ p = [ns.bsfs.Feature, ns.bsfs.Predicate]
+ self.assertEqual(repr(Schema()), f'Schema({n}, {l}, {p})')
+ self.assertEqual(repr(Schema([], [], [])), f'Schema({n}, {l}, {p})')
+ # repr conversion
n = [ns.bsfs.Entity, ns.bsfs.Image, ns.bsfs.Node, ns.bsfs.Tag, ns.bsfs.Unused]
- l = [ns.bsfs.Literal, ns.xsd.boolean, ns.xsd.integer, ns.xsd.string]
- p = [ns.bse.comment, ns.bse.group, ns.bse.tag, ns.bsfs.Predicate]
+ l = [ns.bsfs.Literal, ns.bsfs.Number, ns.xsd.boolean, ns.xsd.integer, ns.xsd.string]
+ p = [ns.bse.comment, ns.bse.group, ns.bse.tag, ns.bsfs.Feature, ns.bsfs.Predicate]
self.assertEqual(repr(Schema(self.predicates, self.nodes, self.literals)), f'Schema({n}, {l}, {p})')
def test_equality(self):
@@ -258,11 +273,11 @@ class TestSchema(unittest.TestCase):
self.assertTrue(operator.lt(Schema({self.p_tag}), Schema({self.p_group})))
self.assertTrue(operator.le(Schema({self.p_tag}), Schema({self.p_group})))
# subset considers differences in predicates and literals
- self.assertTrue(operator.lt(Schema.Empty(), Schema({self.p_comment})))
+ self.assertTrue(operator.lt(Schema(), Schema({self.p_comment})))
# subset considers differences in predicates, nodes, and literals
- self.assertTrue(operator.lt(Schema({}), Schema.Empty()))
- self.assertTrue(operator.lt(Schema({self.p_tag}), Schema.from_string(self.schema_str)))
- self.assertTrue(operator.le(Schema({self.p_tag}), Schema.from_string(self.schema_str)))
+ self.assertTrue(operator.le(Schema({}), Schema()))
+ self.assertTrue(operator.lt(Schema({self.p_tag}), from_string(self.schema_str)))
+ self.assertTrue(operator.le(Schema({self.p_tag}), from_string(self.schema_str)))
self.assertFalse(operator.lt(Schema({self.p_comment}), Schema({self.p_tag})))
self.assertFalse(operator.le(Schema({self.p_comment}), Schema({self.p_tag})))
@@ -280,11 +295,11 @@ class TestSchema(unittest.TestCase):
self.assertTrue(operator.gt(Schema({self.p_group}), Schema({self.p_tag})))
self.assertTrue(operator.ge(Schema({self.p_group}), Schema({self.p_tag})))
# superset considers differences in predicates and literals
- self.assertTrue(operator.gt(Schema({self.p_comment}), Schema.Empty()))
+ self.assertTrue(operator.gt(Schema({self.p_comment}), Schema()))
# superset considers differences in predicates, nodes, and literals
- self.assertTrue(operator.gt(Schema.Empty(), Schema({})))
- self.assertTrue(operator.gt(Schema.from_string(self.schema_str), Schema({self.p_tag})))
- self.assertTrue(operator.ge(Schema.from_string(self.schema_str), Schema({self.p_tag})))
+ self.assertTrue(operator.ge(Schema(), Schema({})))
+ self.assertTrue(operator.gt(from_string(self.schema_str), Schema({self.p_tag})))
+ self.assertTrue(operator.ge(from_string(self.schema_str), Schema({self.p_tag})))
self.assertFalse(operator.gt(Schema({self.p_tag}), Schema({self.p_comment})))
self.assertFalse(operator.ge(Schema({self.p_tag}), Schema({self.p_comment})))
@@ -351,26 +366,26 @@ class TestSchema(unittest.TestCase):
# difference does not contain predicates from the RHS
diff = Schema({self.p_tag, self.p_comment}).diff(Schema({self.p_group}))
self.assertSetEqual(set(diff.nodes), set())
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_string})
+ self.assertSetEqual(set(diff.literals), {self.l_string})
self.assertSetEqual(set(diff.predicates), {self.p_comment})
# difference considers extra nodes and literals
diff = Schema({self.p_tag}, {self.n_unused}, {self.l_unused}).diff(Schema({self.p_tag}))
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_unused})
self.assertSetEqual(set(diff.predicates), set())
# difference considers inconsistent types
diff = Schema({self.p_tag}, {self.n_unused}, {self.l_unused}).diff(
Schema({self.p_tag}, {types.Node(ns.bsfs.Unused, None)}, {types.Literal(ns.xsd.boolean, None)}))
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_unused})
self.assertSetEqual(set(diff.predicates), set())
# __sub__ is an alias for diff
diff = Schema({self.p_comment}, {self.n_unused}, {self.l_unused}) - Schema({self.p_group})
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_string, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_string, self.l_unused})
self.assertSetEqual(set(diff.predicates), {self.p_comment})
# __sub__ only accepts Schema instances
class Foo(): pass
@@ -547,196 +562,6 @@ class TestSchema(unittest.TestCase):
self.assertFalse(schema.has_predicate(ns.bse.mimetype))
self.assertFalse(schema.has_predicate(self.p_root))
- def test_empty(self):
- self.assertEqual(Schema.Empty(), Schema(
- [types.Predicate(ns.bsfs.Predicate, None, types.Node(ns.bsfs.Node, None), None, False)],
- [types.Node(ns.bsfs.Node, None)],
- [types.Literal(ns.bsfs.Literal, None)],
- ))
-
- def test_from_string(self):
- # from_string creates a schema
- self.assertEqual(
- Schema(self.predicates, self.nodes, self.literals),
- Schema.from_string(self.schema_str))
-
- # schema contains at least the root types
- self.assertEqual(Schema.from_string(''), Schema({self.p_root}, {self.n_root}, {self.l_root}))
-
- # custom example
- self.assertEqual(
- Schema({types.Predicate(ns.bsfs.Predicate, None, self.n_root, None, False).get_child(
- ns.bse.filename, self.n_ent, self.l_string, False)}),
- Schema.from_string('''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- xsd:string rdfs:subClassOf bsfs:Literal .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- '''))
-
- # all nodes must be defined
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- xsd:string rdfs:subClassOf bsfs:Literal .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # all literals must be defined
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # must not have circular dependencies
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- # ah, a nice circular dependency
- bsfs:Entity rdfs:subClassOf bsfs:Document .
- bsfs:Document rdfs:subClassOf bsfs:Entity .
- bsfs:PDF rdfs:subClassOf bsfs:Document .
- ''')
-
- # range must be a node or literal
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range bsfs:Foo ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range bsfs:Predicate ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # must be consistent
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- bsfs:Document rdfs:subClassOf bsfs:Node .
- bsfs:Document rdfs:subClassOf bsfs:Entity.
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
-
- xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:name rdfs:subClassOf bsfs:Literal .
- xsd:name rdfs:subClassOf xsd:string .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity .
-
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:range bsfs:Entity .
-
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- bsfs:unique "true"^^xsd:boolean .
-
- ''')
-
-
-
## main ##
if __name__ == '__main__':
diff --git a/test/schema/test_serialize.py b/test/schema/test_serialize.py
new file mode 100644
index 0000000..7392cc0
--- /dev/null
+++ b/test/schema/test_serialize.py
@@ -0,0 +1,1007 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.schema import Schema, types
+from bsfs.utils import errors, URI
+
+# objects to test
+from bsfs.schema.serialize import from_string, to_string
+
+
+## code ##
+
+class TestFromString(unittest.TestCase):
+
+ def test_empty(self):
+ # schema contains at least the root types
+ self.assertEqual(from_string(''), Schema())
+
+
+ def test_circular_dependency(self):
+ # must not have circular dependencies
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ # ah, a nice circular dependency
+ bsfs:Entity rdfs:subClassOf bsfs:Document .
+ bsfs:Document rdfs:subClassOf bsfs:Entity .
+ bsfs:PDF rdfs:subClassOf bsfs:Document .
+ ''')
+
+
+ def test_node(self):
+ # all nodes must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # node definitions must be consistent (cannot re-use a node uri)
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Document rdfs:subClassOf bsfs:Node .
+ bsfs:Document rdfs:subClassOf bsfs:Entity . # conflicting parent
+ ''')
+
+ # additional nodes can be defined
+ n_unused = types.ROOT_NODE.get_child(ns.bsfs.unused)
+ self.assertEqual(Schema({}, {n_unused}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:unused rdfs:subClassOf bsfs:Node . # unused symbol
+ '''))
+
+ # a node can have multiple children
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ n_tag = types.ROOT_NODE.get_child(ns.bsfs.Tag)
+ n_doc = n_ent.get_child(ns.bsfs.Document)
+ n_image = n_ent.get_child(ns.bsfs.Image)
+ self.assertEqual(Schema({}, {n_ent, n_tag, n_doc, n_image}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ # nodes inherit from same parent
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+
+ # nodes inherit from same parent
+ bsfs:Document rdfs:subClassOf bsfs:Entity .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+ '''))
+
+ # additional nodes can be defined and used
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.get_child(ns.xsd.string)
+ p_filename = types.ROOT_PREDICATE.get_child(ns.bse.filename,
+ n_ent, l_string, False)
+ self.assertEqual(Schema({p_filename}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # nodes can have annotations
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ ''').node(ns.bsfs.Entity).annotations, {})
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').node(ns.bsfs.Entity).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_literal(self):
+ # all literals must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ; # undefined symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # literal definitions must be consistent (cannot re-use a literal uri)
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:name rdfs:subClassOf bsfs:Literal .
+ xsd:name rdfs:subClassOf xsd:string . # conflicting parent
+ ''')
+
+ # additional literals can be defined
+ l_unused = types.ROOT_LITERAL.get_child(ns.xsd.unused)
+ self.assertEqual(Schema({}, {}, {l_unused}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ xsd:unused rdfs:subClassOf bsfs:Literal . # unused symbol
+ '''))
+
+ # a literal can have multiple children
+ l_string = types.ROOT_LITERAL.get_child(ns.xsd.string)
+ l_integer = types.ROOT_LITERAL.get_child(ns.xsd.integer)
+ l_unsigned = l_integer.get_child(ns.xsd.unsigned)
+ l_signed = l_integer.get_child(ns.xsd.signed)
+ self.assertEqual(Schema({}, {}, {l_string, l_integer, l_unsigned, l_signed}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ # literals inherit from same parent
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+
+ # literals inherit from same parent
+ xsd:unsigned rdfs:subClassOf xsd:integer .
+ xsd:signed rdfs:subClassOf xsd:integer .
+ '''))
+
+ # additional literals can be defined and used
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.get_child(ns.xsd.string)
+ p_filename = types.ROOT_PREDICATE.get_child(ns.bse.filename,
+ n_ent, l_string, False)
+ self.assertEqual(Schema({p_filename}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # literals can have annotations
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ ''').literal(ns.xsd.string).annotations, {})
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+
+ xsd:string rdfs:subClassOf bsfs:Literal ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').literal(ns.xsd.string).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_predicate(self):
+ # domain must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ; # undefined symbol
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # domain cannot be a literal
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Literal .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ; # literal instead of node
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # range must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ; # undefined symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # range must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Foo ; # undefined symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # range must be a node or a literal
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Predicate ; # invalid symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # additional predicates can be defined
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.get_child(ns.xsd.string)
+ p_comment = types.ROOT_PREDICATE.get_child(ns.bse.comment, domain=n_ent, range=l_string, unique=False)
+ self.assertEqual(Schema({p_comment}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # predicates inherit properties from parents
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.get_child(ns.xsd.string)
+ p_annotation = types.ROOT_PREDICATE.get_child(ns.bsfs.Annotation, domain=n_ent, range=l_string)
+ p_comment = p_annotation.get_child(ns.bse.comment, unique=True)
+ self.assertEqual(Schema({p_comment}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string .
+
+ bse:comment rdfs:subClassOf bsfs:Annotation ; # inherits domain/range from bsfs:Annotation
+ bsfs:unique "true"^^xsd:boolean .
+ '''))
+
+ # we can define partial predicates (w/o specifying a usable range)
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.get_child(ns.xsd.string)
+ p_annotation = types.ROOT_PREDICATE.get_child(ns.bsfs.Annotation, domain=n_ent)
+ p_comment = p_annotation.get_child(ns.bse.comment, range=l_string, unique=False)
+ self.assertEqual(Schema({p_comment}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate ; # derive predicate w/o setting range
+ rdfs:domain bsfs:Entity .
+
+ bse:comment rdfs:subClassOf bsfs:Annotation ; # derived predicate w/ setting range
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # predicate definition can be split across multiple statements.
+ # statements can be repeated
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ p_foo = types.ROOT_PREDICATE.get_child(ns.bse.foo, domain=n_ent, range=types.ROOT_NODE, unique=True)
+ self.assertEqual(Schema({p_foo}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity.
+ '''))
+
+ # domain must be a subtype of parent's domain
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ n_image = n_ent.get_child(ns.bsfs.Image)
+ p_foo = types.ROOT_PREDICATE.get_child(ns.bse.foo, domain=types.ROOT_NODE)
+ p_bar = p_foo.get_child(ns.bse.bar, domain=n_ent)
+ p_foobar = p_bar.get_child(ns.bse.foobar, domain=n_image)
+ self.assertEqual(Schema({p_foobar}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node .
+ bse:bar rdfs:subClassOf bse:foo ;
+ rdfs:domain bsfs:Entity .
+ bse:foobar rdfs:subClassOf bse:bar ;
+ rdfs:domain bsfs:Image .
+ '''))
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Image .
+ bse:bar rdfs:subClassOf bse:foo ;
+ rdfs:domain bsfs:Entity .
+ bse:foobar rdfs:subClassOf bse:bar ;
+ rdfs:domain bsfs:Node .
+ ''')
+
+ # range must be a subtype of parent's range
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ n_image = n_ent.get_child(ns.bsfs.Image)
+ p_foo = types.ROOT_PREDICATE.get_child(ns.bse.foo, range=types.ROOT_NODE)
+ p_bar = p_foo.get_child(ns.bse.bar, range=n_ent)
+ p_foobar = p_bar.get_child(ns.bse.foobar, range=n_image)
+ self.assertEqual(Schema({p_foobar}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Node .
+ bse:bar rdfs:subClassOf bse:foo ;
+ rdfs:range bsfs:Entity .
+ bse:foobar rdfs:subClassOf bse:bar ;
+ rdfs:range bsfs:Image .
+ '''))
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Image .
+ bse:bar rdfs:subClassOf bse:foo ;
+ rdfs:range bsfs:Entity .
+ bse:foobar rdfs:subClassOf bse:bar ;
+ rdfs:range bsfs:Node .
+ ''')
+
+ # cannot define the same predicate from multiple parents
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Annotation ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ # cannot assign multiple conflicting domains to the same predicate
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity . # conflicting domain
+ ''')
+ # cannot assign multiple conflicting ranges to the same predicate
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Entity . # conflicting range
+ ''')
+ # cannot assign multiple conflicting uniques to the same predicate
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ bsfs:unique "true"^^xsd:boolean . # conflicting unique
+ ''')
+
+ # predicates can have annotations
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Node .
+
+ ''').predicate(ns.bse.comment).annotations, {})
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Node ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').predicate(ns.bse.comment).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_feature(self):
+ # domain must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:array rdfs:subClassOf bsfs:Literal .
+
+ bse:colors rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity ; # undefined symbol
+ rdfs:range bsfs:array ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # domain cannot be a literal
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Literal .
+ bsfs:array rdfs:subClassOf bsfs:Literal .
+
+ bse:colors rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity ; # literal instead of node
+ rdfs:range bsfs:array ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # range must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:colors rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:array ; # undefined symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # range must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:colors rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Foo ; # undefined symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # range must be a node or a literal
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:colors rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Predicate ; # invalid symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # additional predicates can be defined
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ l_array = types.ROOT_LITERAL.get_child(ns.bsfs.array)
+ p_comment = types.ROOT_FEATURE.get_child(ns.bse.colors, domain=n_ent, range=l_array, unique=False)
+ self.assertEqual(Schema({p_comment}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:array rdfs:subClassOf bsfs:Literal .
+
+ bse:colors rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:array ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # features inherit properties from parents
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ l_array = types.ROOT_LITERAL.get_child(ns.bsfs.array)
+ l_string = types.ROOT_LITERAL.get_child(ns.xsd.string)
+ p_annotation = types.ROOT_FEATURE.get_child(ns.bsfs.Annotation, domain=n_ent, range=l_array,
+ dimension=1234, dtype=ns.xsd.string)
+ p_comment = p_annotation.get_child(ns.bse.colors, unique=True)
+ self.assertEqual(Schema({p_comment}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:array rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Annotation rdfs:subClassOf bsfs:Feature ; # inherits defaults from bsfs:Feature
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:array ;
+ bsfs:dimension "1234"^^xsd:integer ;
+ bsfs:dtype xsd:string .
+
+ bse:colors rdfs:subClassOf bsfs:Annotation ; # inherits domain/range/etc. from bsfs:Annotation
+ bsfs:unique "true"^^xsd:boolean . # overwrites bsfs:Predicate
+ '''))
+
+ # feature definition can be split across multiple statements.
+ # statements can be repeated
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ p_foo = types.ROOT_FEATURE.get_child(ns.bse.foo, domain=n_ent, unique=True,
+ dimension=1234, dtype=ns.bsfs.f32)
+ self.assertEqual(Schema({p_foo}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ bsfs:unique "true"^^xsd:boolean ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity ;
+ bsfs:dtype bsfs:f32 .
+ '''))
+
+ # cannot define the same feature from multiple parents
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Annotation rdfs:subClassOf bsfs:Feature .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Annotation ;
+ rdfs:domain bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ # cannot assign multiple conflicting domains to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity . # conflicting domain
+ ''')
+ # cannot assign multiple conflicting ranges to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:array rdfs:subClassOf bsfs:Literal .
+ bsfs:large_array rdfs:subClassOf bsfs:array .
+ bsfs:small_array rdfs:subClassOf bsfs:array .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:large_array ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:range bsfs:small_array . # conflicting range
+ ''')
+ # cannot assign multiple conflicting uniques to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ bsfs:unique "true"^^xsd:boolean . # conflicting unique
+ ''')
+ # cannot assign multiple conflicting dimensions to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Node ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "4321"^^xsd:integer . # conflicting dimension
+ ''')
+ # cannot assign multiple conflicting dtypes to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Node ;
+ bsfs:dtype bsfs:f32 .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ bsfs:dtype bsfs:f16 . # conflicting dtype
+ ''')
+ # cannot assign multiple conflicting distance metrics to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Node ;
+ bsfs:distance bsfs:euclidean .
+
+ bse:foo rdfs:subClassOf bsfs:Feature ;
+ bsfs:distance bsfs:cosine . # conflicting distance
+ ''')
+
+ # features can have annotations
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bse:colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ ''').predicate(ns.bse.colors).annotations, {})
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+ bse:colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "1234"^^xsd:integer ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').predicate(ns.bse.colors).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_integration(self):
+ # nodes
+ n_ent = types.ROOT_NODE.get_child(ns.bsfs.Entity)
+ n_tag = types.ROOT_NODE.get_child(ns.bsfs.Tag)
+ n_image = n_ent.get_child(ns.bsfs.Image)
+ # literals
+ l_string = types.ROOT_LITERAL.get_child(ns.xsd.string)
+ l_array = types.ROOT_LITERAL.get_child(ns.bsfs.array)
+ l_integer = types.ROOT_LITERAL.get_child(ns.xsd.integer)
+ l_boolean = types.ROOT_LITERAL.get_child(ns.xsd.boolean)
+ # predicates
+ p_annotation = types.ROOT_PREDICATE.get_child(ns.bsfs.Annotation)
+ p_tag = types.ROOT_PREDICATE.get_child(ns.bse.tag, domain=n_ent, range=n_tag)
+ p_group = p_tag.get_child(ns.bse.group, domain=n_image, unique=True)
+ p_comment = p_annotation.get_child(ns.bse.comment, range=l_string)
+ # features
+ f_colors = types.ROOT_FEATURE.get_child(URI('http://bsfs.ai/schema/Feature/colors_spatial'),
+ domain=n_ent, range=l_array, unique=True, dtype=ns.bsfs.f16, distance=ns.bsfs.euclidean)
+ f_colors1234 = f_colors.get_child(URI('http://bsfs.ai/schema/Feature/colors_spatial#1234'), dimension=1024)
+ f_colors4321 = f_colors.get_child(URI('http://bsfs.ai/schema/Feature/colors_spatial#4321'), dimension=2048)
+ # schema
+ ref = Schema(
+ {p_annotation, p_tag, p_group, p_comment, f_colors, f_colors1234, f_colors4321},
+ {n_ent, n_tag, n_image},
+ {l_string, l_integer, l_boolean})
+ # load from string
+ gen = from_string('''
+ # generic prefixes
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ # bsfs prefixes
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ # nodes
+ bsfs:Entity rdfs:subClassOf bsfs:Node ;
+ rdfs:label "Principal node"^^xsd:string .
+ bsfs:Tag rdfs:subClassOf bsfs:Node ;
+ rdfs:label "Tag"^^xsd:string .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ # literals
+ xsd:string rdfs:subClassOf bsfs:Literal ;
+ rdfs:label "A sequence of characters"^^xsd:string .
+ bsfs:array rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+
+ # abstract predicates
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate ;
+ rdfs:label "node annotation"^^xsd:string .
+ bsfs:Feature rdfs:subClassOf bsfs:Predicate .
+
+ # feature instances
+ <http://bsfs.ai/schema/Feature/colors_spatial> rdfs:subClassOf bsfs:Feature ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:array ;
+ bsfs:unique "true"^^xsd:boolean ;
+ bsfs:dtype bsfs:f16 ;
+ bsfs:distance bsfs:euclidean ;
+ # annotations
+ rdfs:label "ColorsSpatial instances. Dimension depends on instance."^^xsd:string ;
+ bsfs:first_arg "1234"^^xsd:integer ;
+ bsfs:second_arg "hello world"^^xsd:string .
+
+ <http://bsfs.ai/schema/Feature/colors_spatial#1234> rdfs:subClassOf <http://bsfs.ai/schema/Feature/colors_spatial> ;
+ bsfs:dimension "1024"^^xsd:integer ;
+ rdfs:label "Main colors spatial instance"^^xsd:string .
+
+ <http://bsfs.ai/schema/Feature/colors_spatial#4321> rdfs:subClassOf <http://bsfs.ai/schema/Feature/colors_spatial> ;
+ bsfs:dimension "2048"^^xsd:integer .
+
+ # predicate instances
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean ;
+ # annotations
+ rdfs:label "connect entity to a tag"^^xsd:string .
+
+ bse:group rdfs:subClassOf bse:tag ; # subtype of another predicate
+ rdfs:domain bsfs:Image ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:comment rdfs:subClassOf bsfs:Annotation ; # subtype of abstract predicate
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ # schemas are equal
+ self.assertEqual(ref, gen)
+ # check annotations
+ self.assertDictEqual(gen.node(ns.bsfs.Entity).annotations, {ns.rdfs.label: 'Principal node'})
+ self.assertDictEqual(gen.node(ns.bsfs.Tag).annotations, {ns.rdfs.label: 'Tag'})
+ self.assertDictEqual(gen.literal(ns.xsd.string).annotations, {ns.rdfs.label: 'A sequence of characters'})
+ self.assertDictEqual(gen.predicate(ns.bsfs.Annotation).annotations, {ns.rdfs.label: 'node annotation'})
+ self.assertDictEqual(gen.predicate(URI('http://bsfs.ai/schema/Feature/colors_spatial')).annotations, {
+ ns.rdfs.label: 'ColorsSpatial instances. Dimension depends on instance.',
+ ns.bsfs.first_arg: 1234,
+ ns.bsfs.second_arg: 'hello world',
+ })
+ self.assertDictEqual(gen.predicate(URI('http://bsfs.ai/schema/Feature/colors_spatial#1234')).annotations, {
+ ns.rdfs.label: 'Main colors spatial instance'})
+ self.assertDictEqual(gen.predicate(ns.bse.tag).annotations, {ns.rdfs.label: 'connect entity to a tag'})
+
+
+
+class TestToString(unittest.TestCase):
+ def test_stub(self):
+ raise NotImplementedError()
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/schema/test_types.py b/test/schema/test_types.py
index 4a49e6e..af47f0d 100644
--- a/test/schema/test_types.py
+++ b/test/schema/test_types.py
@@ -10,15 +10,17 @@ import unittest
# bsfs imports
from bsfs.namespace import ns
+from bsfs.schema.types import ROOT_PREDICATE, ROOT_VERTEX, ROOT_FEATURE
from bsfs.utils import errors
# objects to test
-from bsfs.schema.types import _Type, _Vertex, Node, Literal, Predicate
+from bsfs.schema.types import _Type, _Vertex, Node, Literal, Predicate, Feature
## code ##
class TestType(unittest.TestCase):
+
def test_parents(self):
# create some types
fst = _Type('First')
@@ -31,7 +33,25 @@ class TestType(unittest.TestCase):
self.assertListEqual(list(trd.parents()), [snd, fst])
self.assertListEqual(list(frd.parents()), [trd, snd, fst])
- def test_essentials(self):
+ def test_annotations(self):
+ # annotations can be empty
+ self.assertDictEqual(_Type('Foo', None).annotations, {})
+ # annotations are stored
+ self.assertDictEqual(_Type('Foo', None, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123})
+ # comparison ignores annotations
+ self.assertEqual(
+ _Type('Foo', None, foo='bar', bar='foo'),
+ _Type('Foo', None, hello='world', foobar=1234))
+ self.assertEqual(
+ hash(_Type('Foo', None, foo='bar', bar='foo')),
+ hash(_Type('Foo', None, hello='world', foobar=1234)))
+ # annotations can be passed to get_child
+ self.assertDictEqual(_Type('First', foo='bar').get_child('Second', bar='foo').annotations, {
+ 'bar': 'foo'})
+
+ def test_string_conversion(self):
# type w/o parent
self.assertEqual(str(_Type('Foo')), '_Type(Foo)')
self.assertEqual(repr(_Type('Foo')), '_Type(Foo, None)')
@@ -59,6 +79,9 @@ class TestType(unittest.TestCase):
# type persists
class Foo(_Type): pass
self.assertEqual(Foo('First').get_child('Second'), Foo('Second', Foo('First')))
+ # annotations are respected
+ self.assertDictEqual(_Type('First', foo='bar').get_child('Second', bar='foo').annotations, {
+ 'bar': 'foo'})
def test_equality(self):
# equality depends on uri
@@ -76,6 +99,13 @@ class TestType(unittest.TestCase):
# comparison respects parent
self.assertNotEqual(_Type('Foo', _Type('Bar')), _Type('Foo'))
self.assertNotEqual(hash(_Type('Foo', _Type('Bar'))), hash(_Type('Foo')))
+ # comparison ignores annotations
+ self.assertEqual(
+ _Type('Foo', None, foo='bar', bar='foo'),
+ _Type('Foo', None, hello='world', foobar=1234))
+ self.assertEqual(
+ hash(_Type('Foo', None, foo='bar', bar='foo')),
+ hash(_Type('Foo', None, hello='world', foobar=1234)))
def test_order(self):
# create some types.
@@ -109,25 +139,40 @@ class TestType(unittest.TestCase):
self.assertFalse(bike > bicycle)
self.assertFalse(bike >= bicycle)
self.assertFalse(bike == bicycle)
+
+ # can compare types along the class hierarchy
class Foo(_Type): pass
- foo = Foo(bike.uri, bike.parent)
- # cannot compare different types
- self.assertRaises(TypeError, operator.lt, foo, bike)
- self.assertRaises(TypeError, operator.le, foo, bike)
- self.assertRaises(TypeError, operator.gt, foo, bike)
- self.assertRaises(TypeError, operator.ge, foo, bike)
+ foo = Foo('Foo', bike)
+ self.assertTrue(foo < bike)
+ self.assertTrue(foo <= bike)
+ self.assertFalse(foo > bike)
+ self.assertFalse(foo >= bike)
# goes both ways
- self.assertRaises(TypeError, operator.lt, bike, foo)
- self.assertRaises(TypeError, operator.le, bike, foo)
- self.assertRaises(TypeError, operator.gt, bike, foo)
- self.assertRaises(TypeError, operator.ge, bike, foo)
+ self.assertFalse(bike < foo)
+ self.assertFalse(bike <= foo)
+ self.assertTrue(bike > foo)
+ self.assertTrue(bike >= foo)
+ # cannot compare unrelated classes
+ class Bar(_Type): pass
+ bar = Bar('Bar', bike)
+ self.assertRaises(TypeError, operator.lt, foo, bar)
+ self.assertRaises(TypeError, operator.le, foo, bar)
+ self.assertRaises(TypeError, operator.gt, foo, bar)
+ self.assertRaises(TypeError, operator.ge, foo, bar)
+ # goes both ways
+ self.assertRaises(TypeError, operator.lt, bar, foo)
+ self.assertRaises(TypeError, operator.le, bar, foo)
+ self.assertRaises(TypeError, operator.gt, bar, foo)
+ self.assertRaises(TypeError, operator.ge, bar, foo)
+
class TestPredicate(unittest.TestCase):
def test_construction(self):
# domain must be a node
self.assertRaises(TypeError, Predicate, ns.bse.foo, 1234, None, True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Literal(ns.bsfs.Foo, None), None, True)
- # range must be None, a Literal, or a Node
+ # range must be a Literal, a Node, or the root Vertex
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), None, True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), 1234, True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), _Vertex(ns.bsfs.Foo, None), True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), _Type(ns.bsfs.Foo, None), True)
@@ -138,54 +183,52 @@ class TestPredicate(unittest.TestCase):
n_root = Node(ns.bsfs.Node, None)
n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
n_tag = Node(ns.bsfs.Tag, Node(ns.bsfs.Tag, None))
- root = Predicate(
- uri=ns.bsfs.Predicate,
- parent=None,
+ root = ROOT_PREDICATE
+ tag = Predicate(
+ uri=ns.bse.tag,
+ parent=root,
domain=n_root,
- range=None,
+ range=n_tag,
unique=False,
)
# instance is equal to itself
- self.assertEqual(root, root)
- self.assertEqual(hash(root), hash(root))
+ self.assertEqual(tag, tag)
+ self.assertEqual(hash(tag), hash(tag))
# instance is equal to a clone
- self.assertEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, None, False))
- self.assertEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, None, False)))
+ self.assertEqual(tag, Predicate(ns.bse.tag, root, n_root, n_tag, False))
+ self.assertEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_tag, False)))
# equality respects uri
- self.assertNotEqual(root, Predicate(ns.bsfs.Alternative, None, n_root, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Alternative, None, n_root, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bsfs.Alternative, root, n_root, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bsfs.Alternative, root, n_root, n_tag, False)))
# equality respects parent
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, n_root, n_root, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, n_root, n_root, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, n_root, n_root, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, n_root, n_root, n_tag, False)))
# equality respects domain
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_ent, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_ent, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_ent, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_ent, n_tag, False)))
# equality respects range
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, n_root, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, n_root, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_root, n_root, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_root, False)))
# equality respects unique
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, None, True))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, None, True)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_root, n_tag, True))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_tag, True)))
def test_get_child(self):
n_root = Node(ns.bsfs.Node, None)
+ l_root = Literal(ns.bsfs.Literal, None)
n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
n_tag = Node(ns.bsfs.Tag, Node(ns.bsfs.Tag, None))
- root = Predicate(
- uri=ns.bsfs.Predicate,
- parent=None,
- domain=n_root,
- range=None,
- unique=False,
- )
+ root = ROOT_PREDICATE
tag = Predicate(
- uri=ns.bsfs.Entity,
+ uri=ns.bse.tag,
parent=root,
domain=n_ent,
range=n_tag,
unique=False,
)
+ # get_child returns Predicate
+ self.assertIsInstance(tag.get_child(ns.bse.foo), Predicate)
# uri is respected
self.assertEqual(ns.bse.foo, tag.get_child(ns.bse.foo).uri)
# domain is respected
@@ -198,10 +241,17 @@ class TestPredicate(unittest.TestCase):
self.assertEqual(n_tag, tag.get_child(ns.bse.foo, range=None).range)
# unique is respected
self.assertTrue(tag.get_child(ns.bse.foo, unique=True).unique)
+ # annotations are respected
+ self.assertDictEqual(tag.get_child(ns.bse.foo, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123,
+ })
# domain is inherited from parent
+ self.assertEqual(n_root, root.get_child(ns.bse.foo).domain)
self.assertEqual(n_ent, tag.get_child(ns.bse.foo).domain)
# range is inherited from parent
+ self.assertEqual(ROOT_VERTEX, root.get_child(ns.bse.foo).range)
self.assertEqual(n_tag, tag.get_child(ns.bse.foo).range)
# uniqueness is inherited from parent
self.assertFalse(tag.get_child(ns.bse.foo).unique)
@@ -209,11 +259,118 @@ class TestPredicate(unittest.TestCase):
# domain must be subtype of parent's domain
self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, domain=n_root)
self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, domain=Node(ns.bsfs.Image, n_root))
- # range cannot be None
- self.assertRaises(ValueError, root.get_child, ns.bse.foo)
# range must be subtype of parent's range
self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, range=n_root)
self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, range=Node(ns.bsfs.Image, n_root))
+ self.assertRaises(TypeError, tag.get_child, ns.bse.foo, range=Literal(ns.bsfs.Tag, l_root))
+ # range can be subtyped from ROOT_VERTEX to Node or Literal
+ self.assertEqual(n_root, root.get_child(ns.bse.foo, range=n_root).range)
+ self.assertEqual(l_root, root.get_child(ns.bse.foo, range=l_root).range)
+
+
+class TestFeature(unittest.TestCase):
+ def test_construction(self):
+ n_root = Node(ns.bsfs.Node, None)
+ l_root = Literal(ns.bsfs.Literal, None)
+ # dimension, dtype, and distance are respected
+ feat = Feature(ns.bsfs.Feature, None, n_root, l_root, False,
+ 1234, ns.bsfs.float, ns.bsfs.euclidean)
+ self.assertEqual(1234, feat.dimension)
+ self.assertEqual(ns.bsfs.float, feat.dtype)
+ self.assertEqual(ns.bsfs.euclidean, feat.distance)
+
+ def test_equality(self):
+ n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
+ l_array = Literal(ns.bsfs.array, Literal(ns.bsfs.Literal, None))
+ colors = Feature(
+ uri=ns.bse.colors,
+ parent=ROOT_FEATURE,
+ domain=n_ent,
+ range=l_array,
+ unique=False,
+ dimension=1234,
+ dtype=ns.bsfs.float,
+ distance=ns.bsfs.euclidean,
+ )
+ # instance is equal to itself
+ self.assertEqual(colors, colors)
+ self.assertEqual(hash(colors), hash(colors))
+ # instance is equal to a clone
+ self.assertEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, n_ent, l_array, False, 1234, ns.bsfs.float, ns.bsfs.euclidean))
+ self.assertEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, n_ent, l_array, False, 1234, ns.bsfs.float, ns.bsfs.euclidean)))
+ # equality respects dimension
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, n_ent, l_array, False, 4321, ns.bsfs.float, ns.bsfs.euclidean))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, n_ent, l_array, False, 4321, ns.bsfs.float, ns.bsfs.euclidean)))
+ # equality respects dtype
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, n_ent, l_array, False, 1234, ns.bsfs.integer, ns.bsfs.euclidean))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, n_ent, l_array, False, 1234, ns.bsfs.integer, ns.bsfs.euclidean)))
+ # equality respects distance
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, n_ent, l_array, False, 1234, ns.bsfs.float, ns.bsfs.cosine))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, n_ent, l_array, False, 1234, ns.bsfs.float, ns.bsfs.cosine)))
+
+ def test_get_child(self):
+ n_root = Node(ns.bsfs.Node, None)
+ n_ent = Node(ns.bsfs.Entity, n_root)
+ l_root = Literal(ns.bsfs.Literal, None)
+ l_array = Literal(ns.bsfs.array, l_root)
+ colors = Feature(
+ uri=ns.bse.colors,
+ parent=ROOT_FEATURE,
+ domain=n_ent,
+ range=l_array,
+ unique=False,
+ dimension=1234,
+ dtype=ns.bsfs.float,
+ distance=ns.bsfs.euclidean,
+ )
+
+ # get_child returns Feature
+ self.assertIsInstance(colors.get_child(ns.bse.foo), Feature)
+ # uri is respected
+ self.assertEqual(ns.bse.foo, colors.get_child(ns.bse.foo).uri)
+ # domain is respected
+ dom = Node(ns.bsfs.Image, n_ent)
+ self.assertEqual(dom, colors.get_child(ns.bse.foo, domain=dom).domain)
+ # range is respected
+ rng = Literal(ns.bse.foo, l_array)
+ self.assertEqual(rng, colors.get_child(ns.bse.foo, range=rng).range)
+ # cannot set range to None
+ self.assertEqual(l_array, colors.get_child(ns.bse.foo, range=None).range)
+ # unique is respected
+ self.assertTrue(colors.get_child(ns.bse.foo, unique=True).unique)
+ # dimension is respected
+ self.assertEqual(4321, colors.get_child(ns.bse.foo, dimension=4321).dimension)
+ # dtype is respected
+ self.assertEqual(ns.bsfs.integer, colors.get_child(ns.bse.foo, dtype=ns.bsfs.integer).dtype)
+ # distance is respected
+ self.assertEqual(ns.bsfs.cosine, colors.get_child(ns.bse.foo, distance=ns.bsfs.cosine).distance)
+ # annotations are respected
+ self.assertDictEqual(colors.get_child(ns.bse.foo, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123,
+ })
+
+ # domain is inherited from parent
+ self.assertEqual(n_root, ROOT_FEATURE.get_child(ns.bse.foo).domain)
+ self.assertEqual(n_ent, colors.get_child(ns.bse.foo).domain)
+ # range is inherited from parent
+ self.assertEqual(l_array, colors.get_child(ns.bse.foo).range)
+ # uniqueness is inherited from parent
+ self.assertFalse(colors.get_child(ns.bse.foo).unique)
+ # dimension is inherited from parent
+ self.assertEqual(1234, colors.get_child(ns.bse.foo).dimension)
+ # dtype is inherited from parent
+ self.assertEqual(ns.bsfs.float, colors.get_child(ns.bse.foo).dtype)
+ # distance is inherited from parent
+ self.assertEqual(ns.bsfs.euclidean, colors.get_child(ns.bse.foo).distance)
+
+ # domain must be subtype of parent's domain
+ self.assertRaises(errors.ConsistencyError, colors.get_child, ns.bse.foo, domain=n_root)
+ self.assertRaises(errors.ConsistencyError, colors.get_child, ns.bse.foo, domain=Node(ns.bsfs.Image, n_root))
+ # range must be subtype of parent's range
+ self.assertRaises(errors.ConsistencyError, colors.get_child, ns.bse.foo, range=Literal(ns.bsfs.Literal, None))
+ self.assertRaises(errors.ConsistencyError, colors.get_child, ns.bse.foo, range=Literal(ns.bsfs.foo, Literal(ns.bsfs.Literal, None)))
+ self.assertRaises(TypeError, colors.get_child, ns.bse.foo, range=Node(ns.bsfs.Tag, n_root))
## main ##
@@ -222,4 +379,3 @@ if __name__ == '__main__':
unittest.main()
## EOF ##
-