aboutsummaryrefslogtreecommitdiffstats
path: root/test/schema/test_serialize.py
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-03-05 19:25:29 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-03-05 19:25:29 +0100
commit48b6081d0092e9c5a1b0ad79bdde2e51649bf61a (patch)
tree634198c34aae3c0306ce30ac7452abd7b53a14e8 /test/schema/test_serialize.py
parent91437ba89d35bf482f3d9671bb99ef2fc69f5985 (diff)
parente4845c627e97a6d125bf33d9e7a4a8d373d7fc4a (diff)
downloadbsfs-48b6081d0092e9c5a1b0ad79bdde2e51649bf61a.tar.gz
bsfs-48b6081d0092e9c5a1b0ad79bdde2e51649bf61a.tar.bz2
bsfs-48b6081d0092e9c5a1b0ad79bdde2e51649bf61a.zip
Merge branch 'develop'v0.23.03
Diffstat (limited to 'test/schema/test_serialize.py')
-rw-r--r--test/schema/test_serialize.py1048
1 files changed, 1048 insertions, 0 deletions
diff --git a/test/schema/test_serialize.py b/test/schema/test_serialize.py
new file mode 100644
index 0000000..7d5d3ae
--- /dev/null
+++ b/test/schema/test_serialize.py
@@ -0,0 +1,1048 @@
+
+# imports
+import re
+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 ##
+
+ns.bse = ns.bsfs.Entity()
+
+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: <https://schema.bsfs.io/core/>
+ 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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+
+ 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.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:unused rdfs:subClassOf bsfs:Node . # unused symbol
+ '''))
+
+ # a node can have multiple children
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ n_tag = types.ROOT_NODE.child(ns.bsfs.Tag)
+ n_doc = n_ent.child(ns.bsfs.Document)
+ n_image = n_ent.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_filename = types.ROOT_PREDICATE.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+
+ 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: <https://schema.bsfs.io/core/>
+
+ 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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+
+ 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.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ xsd:unused rdfs:subClassOf bsfs:Literal . # unused symbol
+ '''))
+
+ # a literal can have multiple children
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ l_integer = types.ROOT_NUMBER.child(ns.xsd.integer)
+ l_unsigned = l_integer.child(ns.xsd.unsigned)
+ l_signed = l_integer.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+
+ # literals inherit from same parent
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
+
+ # 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.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_filename = types.ROOT_PREDICATE.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+
+ 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: <https://schema.bsfs.io/core/>
+
+ 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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_comment = types.ROOT_PREDICATE.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation, domain=n_ent, range=l_string)
+ p_comment = p_annotation.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation, domain=n_ent)
+ p_comment = p_annotation.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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.child(ns.bsfs.Entity)
+ p_foo = types.ROOT_PREDICATE.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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.child(ns.bsfs.Entity)
+ n_image = n_ent.child(ns.bsfs.Image)
+ p_foo = types.ROOT_PREDICATE.child(ns.bse.foo, domain=types.ROOT_NODE)
+ p_bar = p_foo.child(ns.bse.bar, domain=n_ent)
+ p_foobar = p_bar.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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.child(ns.bsfs.Entity)
+ n_image = n_ent.child(ns.bsfs.Image)
+ p_foo = types.ROOT_PREDICATE.child(ns.bse.foo, range=types.ROOT_NODE)
+ p_bar = p_foo.child(ns.bse.bar, range=n_ent)
+ p_foobar = p_bar.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/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):
+ # additional features can be defined
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors)
+ self.assertEqual(Schema(literals={f_colors}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature .
+
+ '''))
+
+ # features inherit properties from parents
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.dtype().i32)
+ f_main_colors = f_colors.child(ns.bsfs.MainColor, distance=ns.bsfs.cosine, dtype=ns.bsfs.dtype().f16)
+ self.assertEqual(Schema(literals={f_colors, f_main_colors}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ; # inherits distance from bsa:Feature
+ bsfs:dimension "1234"^^xsd:integer ; # overwrites bsa:Feature
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#i32> . # overwrites bsa:Feature
+
+ bsfs:MainColor rdfs:subClassOf bsfs:Colors ; # inherits dimension from bsfs:Colors
+ bsfs:distance bsfs:cosine ; # overwrites bsa:Feature
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f16> . # overwrites bsfs:Colors
+
+ '''))
+
+ # feature definition can be split across multiple statements.
+ # statements can be repeated
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.dtype().f32)
+ self.assertEqual(Schema(literals={f_colors}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer ; # non-conflicting repetition
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+ bsfs:ColorSpace rdfs:subClassOf bsa:Feature .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature .
+ bsfs:Colors rdfs:subClassOf bsfs:ColorSpace .
+
+ ''')
+ # 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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ bsfs:Colors rdfs:subClassOf bsa: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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#i32> .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:distance bsfs:euclidean .
+
+ bsfs:Colors rdfs:subClassOf bsa: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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ ''').literal(ns.bsfs.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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').literal(ns.bsfs.Colors).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_integration(self):
+ # nodes
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ n_tag = types.ROOT_NODE.child(ns.bsfs.Tag)
+ n_image = n_ent.child(ns.bsfs.Image)
+ # literals
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ l_array = types.ROOT_LITERAL.child(ns.bsfs.array)
+ l_integer = types.ROOT_NUMBER.child(ns.xsd.integer)
+ l_boolean = types.ROOT_LITERAL.child(ns.xsd.boolean)
+ # predicates
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation)
+ p_tag = types.ROOT_PREDICATE.child(ns.bse.tag, domain=n_ent, range=n_tag)
+ p_group = p_tag.child(ns.bse.group, domain=n_image, unique=True)
+ p_comment = p_annotation.child(ns.bse.comment, range=l_string)
+ # features
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial'),
+ dtype=ns.bsfs.dtype().f16, distance=ns.bsfs.euclidean)
+ f_colors1234 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial#1234'), dimension=1024)
+ f_colors4321 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial#4321'), dimension=2048)
+ # schema
+ ref = Schema(
+ {p_annotation, p_tag, p_group, p_comment},
+ {n_ent, n_tag, n_image},
+ {l_string, l_integer, l_boolean, f_colors, f_colors1234, f_colors4321})
+ # 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: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ # 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 .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+
+
+ # abstract predicates
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate ;
+ rdfs:label "node annotation"^^xsd:string .
+
+ # feature instances
+ <https://schema.bsfs.io/core/Feature/colors_spatial> rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#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 .
+
+ <https://schema.bsfs.io/core/Feature/colors_spatial#1234> rdfs:subClassOf <https://schema.bsfs.io/core/Feature/colors_spatial> ;
+ bsfs:dimension "1024"^^xsd:integer ;
+ rdfs:label "Main colors spatial instance"^^xsd:string .
+
+ <https://schema.bsfs.io/core/Feature/colors_spatial#4321> rdfs:subClassOf <https://schema.bsfs.io/core/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.literal(URI('https://schema.bsfs.io/core/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.literal(URI('https://schema.bsfs.io/core/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'})
+
+ # blank nodes result in an error
+ self.assertRaises(errors.BackendError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ bsfs:Entity rdfs:subClassOf bsfs:Node ;
+ bsfs:foo _:bar .
+ ''')
+
+
+class TestToString(unittest.TestCase):
+
+ def test_empty(self):
+ self.assertEqual(Schema(), from_string(to_string(Schema())))
+
+ def test_parse(self):
+ schema = Schema()
+ schema._nodes[ns.bsfs.Invalid] = 123 # NOTE: Access protected to force an invalid schema
+ self.assertRaises(TypeError, to_string, schema)
+
+ def test_literal(self):
+ # root literals
+ l_str = types.ROOT_LITERAL.child(ns.xsd.string)
+ # derived literals
+ l_int = types.ROOT_NUMBER.child(ns.xsd.integer)
+ l_unsigned = l_int.child(ns.xsd.unsigned)
+ # create schema
+ schema = Schema(literals={l_int, l_str, l_unsigned})
+
+ schema_str = to_string(schema)
+ # all symbols are serialized
+ self.assertIn('xsd:string', schema_str)
+ self.assertIn('xsd:integer', schema_str)
+ self.assertIn('xsd:unsigned', schema_str)
+ # unserialize yields the original schema
+ self.assertEqual(schema, from_string(schema_str))
+
+ # literals that have no parent are ignored
+ schema = Schema(literals={types.Literal(ns.bsfs.Invalid, None)})
+ self.assertEqual(Schema(), from_string(to_string(schema)))
+ self.assertNotIn('Invalid', to_string(schema))
+
+ # literal annotations are serialized
+ annotations = {
+ ns.rdfs.label: 'hello world',
+ ns.schema.description: 'some text',
+ ns.bsfs.foo: 1234,
+ ns.bsfs.bar: True,
+ }
+ l_str = types.ROOT_LITERAL.child(ns.xsd.string, **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema(literals={l_str}))).literal(ns.xsd.string).annotations)
+
+
+ def test_node(self):
+ # root nodes
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ n_tag = types.ROOT_NODE.child(ns.bsfs.Tag)
+ # derived nodes
+ n_img = n_ent.child(ns.bsfs.Image)
+ n_doc = n_ent.child(ns.bsfs.Document)
+ n_grp = n_tag.child(ns.bsfs.Group)
+ # create schema
+ schema = Schema(nodes={n_ent, n_img, n_doc, n_tag, n_grp})
+
+ schema_str = to_string(schema)
+ # all symbols are serialized
+ self.assertIn('bsfs:Entity', schema_str)
+ self.assertIn('bsfs:Tag', schema_str)
+ self.assertIn('bsfs:Image', schema_str)
+ self.assertIn('bsfs:Document', schema_str)
+ self.assertIn('bsfs:Group', schema_str)
+ # unserialize yields the original schema
+ self.assertEqual(schema, from_string(schema_str))
+
+ # nodes that have no parent are ignored
+ schema = Schema(nodes={types.Node(ns.bsfs.Invalid, None)})
+ self.assertEqual(Schema(), from_string(to_string(schema)))
+ self.assertNotIn('Invalid', to_string(schema))
+
+ # node annotations are serialized
+ annotations = {
+ ns.rdfs.label: 'hello world',
+ ns.schema.description: 'some text',
+ ns.bsfs.foo: 1234,
+ ns.bsfs.bar: True,
+ }
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity, **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema(nodes={n_ent}))).node(ns.bsfs.Entity).annotations)
+
+
+ def test_predicate(self):
+ # auxiliary types
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ l_str = types.ROOT_LITERAL.child(ns.xsd.string)
+ # root predicates
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation, domain=n_ent)
+ p_owner = types.ROOT_PREDICATE.child(ns.bse.owner, range=l_str, unique=True)
+ # derived predicates
+ p_comment = p_annotation.child(ns.bse.comment, range=l_str) # inherits domain
+ p_note = p_comment.child(ns.bse.note, unique=True) # inherits domain/range
+ # create schema
+ schema = Schema({p_owner, p_comment, p_note})
+
+ schema_str = to_string(schema)
+ # all symbols are serialized
+ self.assertIn('bsfs:Entity', schema_str)
+ self.assertIn('xsd:string', schema_str)
+ self.assertIn('bsfs:Annotation', schema_str)
+ self.assertIn('bse:comment', schema_str)
+ self.assertIn('bse:owner', schema_str)
+ self.assertIn('bse:note', schema_str)
+ # inherited properties are not serialized
+ self.assertIsNotNone(re.search(r'bse:comment[^\.]*rdfs:range[^\.]', schema_str))
+ self.assertIsNone(re.search(r'bse:comment[^\.]*rdfs:domain[^\.]', schema_str))
+ #p_note has no domain/range
+ self.assertIsNone(re.search(r'bse:note[^\.]*rdfs:domain[^\.]', schema_str))
+ self.assertIsNone(re.search(r'bse:note[^\.]*rdfs:range[^\.]', schema_str))
+ # unserialize yields the original schema
+ self.assertEqual(schema, from_string(schema_str))
+
+ # predicate annotations are serialized
+ annotations = {
+ ns.rdfs.label: 'hello world',
+ ns.schema.description: 'some text',
+ ns.bsfs.foo: 1234,
+ ns.bsfs.bar: False,
+ }
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation, **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema({p_annotation}))).predicate(ns.bsfs.Annotation).annotations)
+
+
+ def test_feature(self):
+ # root features
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors'),
+ distance=ns.bsfs.cosine)
+ # derived features
+ f_colors1234 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors#1234'),
+ dimension=1024) # inherits dtype, distance
+ f_colors4321 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors#4321'),
+ dimension=2048, distance=ns.bsfs.euclidean) # inherits dtype
+ # create schema
+ schema = Schema(literals={f_colors, f_colors1234, f_colors4321})
+
+ schema_str = to_string(schema)
+ # all symbols are serialized
+ self.assertIn('bsl:Array', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors#1234', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors#4321', schema_str)
+ # inherited properties are not serialized
+ self.assertIsNotNone(re.search(r'<https://schema.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:dimension[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:dtype[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:distance[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:dimension[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:distance[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:dtype[^\.]', schema_str))
+ # unserialize yields the original schema
+ self.assertEqual(schema, from_string(schema_str))
+
+ # predicate annotations are serialized
+ annotations = {
+ ns.rdfs.label: 'hello world',
+ ns.schema.description: 'some text',
+ ns.bsfs.foo: 1234,
+ ns.bsfs.bar: False,
+ }
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors'),
+ dtype=ns.bsfs.dtype().f16, distance=ns.bsfs.euclidean,
+ **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema(literals={f_colors}))).literal(URI('https://schema.bsfs.io/core/Feature/colors')).annotations)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##