diff options
Diffstat (limited to 'test/schema/test_serialize.py')
-rw-r--r-- | test/schema/test_serialize.py | 1007 |
1 files changed, 1007 insertions, 0 deletions
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 ## |