# 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: prefix bsfs: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: # 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: bsfs:Entity rdfs:subClassOf bsfs:Node . ''').node(ns.bsfs.Entity).annotations, {}) self.assertDictEqual(from_string(''' prefix rdfs: prefix xsd: prefix bsfs: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: prefix bsl: # 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: xsd:string rdfs:subClassOf bsfs:Literal . ''').literal(ns.xsd.string).annotations, {}) self.assertDictEqual(from_string(''' prefix rdfs: prefix xsd: prefix bsfs: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: bse:comment rdfs:subClassOf bsfs:Predicate ; rdfs:range bsfs:Node . ''').predicate(ns.bse.comment).annotations, {}) self.assertDictEqual(from_string(''' prefix rdfs: prefix xsd: prefix bsfs: prefix bse: 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: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: 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: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: 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 . # overwrites bsa:Feature bsfs:MainColor rdfs:subClassOf bsfs:Colors ; # inherits dimension from bsfs:Colors bsfs:distance bsfs:cosine ; # overwrites bsa:Feature bsfs:dtype . # 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: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: 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 . ''')) # cannot define the same feature from multiple parents self.assertRaises(errors.ConsistencyError, from_string, ''' prefix rdfs: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: 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: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: 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: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: bsl:Array rdfs:subClassOf bsfs:Literal . bsa:Feature rdfs:subClassOf bsl:Array. bsfs:Colors rdfs:subClassOf bsa:Feature ; bsfs:dtype . bsfs:Colors rdfs:subClassOf bsa:Feature ; bsfs:dtype . # conflicting dtype ''') # cannot assign multiple conflicting distance metrics to the same feature self.assertRaises(errors.ConsistencyError, from_string, ''' prefix rdfs: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: 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: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: 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: prefix xsd: prefix bsfs: prefix bse: prefix bsl: prefix bsa: 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: prefix xsd: # bsfs prefixes prefix bsfs: prefix bse: prefix bsl: prefix bsa: # 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 rdfs:subClassOf bsa:Feature ; bsfs:dtype ; 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 . rdfs:subClassOf ; bsfs:dimension "1024"^^xsd:integer ; rdfs:label "Main colors spatial instance"^^xsd:string . rdfs:subClassOf ; 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: prefix bsfs: 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('.*[^\.]*bsfs:dimension[^\.]', schema_str)) self.assertIsNone(re.search(r'.*[^\.]*bsfs:dtype[^\.]', schema_str)) self.assertIsNone(re.search(r'.*[^\.]*bsfs:distance[^\.]', schema_str)) self.assertIsNotNone(re.search(r'.*[^\.]*bsfs:dimension[^\.]', schema_str)) self.assertIsNotNone(re.search(r'.*[^\.]*bsfs:distance[^\.]', schema_str)) self.assertIsNone(re.search(r'.*[^\.]*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 ##