aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/apps/schema-2.nt3
-rw-r--r--test/apps/test_migrate.py10
-rw-r--r--test/graph/ac/test_null.py7
-rw-r--r--test/graph/test_graph.py24
-rw-r--r--test/graph/test_nodes.py11
-rw-r--r--test/graph/test_resolve.py24
-rw-r--r--test/namespace/test_namespace.py12
-rw-r--r--test/query/ast_test/__init__.py (renamed from test/query/ast/__init__.py)0
-rw-r--r--test/query/ast_test/test_filter_.py (renamed from test/query/ast/test_filter_.py)35
-rw-r--r--test/query/test_validator.py59
-rw-r--r--test/schema/test_schema.py322
-rw-r--r--test/schema/test_serialize.py1030
-rw-r--r--test/schema/test_types.py243
-rw-r--r--test/triple_store/sparql/test_distance.py61
-rw-r--r--test/triple_store/sparql/test_parse_filter.py62
-rw-r--r--test/triple_store/sparql/test_sparql.py59
-rw-r--r--test/utils/test_uuid.py4
17 files changed, 1590 insertions, 376 deletions
diff --git a/test/apps/schema-2.nt b/test/apps/schema-2.nt
index 525ac99..4c5468f 100644
--- a/test/apps/schema-2.nt
+++ b/test/apps/schema-2.nt
@@ -10,7 +10,8 @@ prefix bse: <http://bsfs.ai/schema/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
# common definitions
-xsd:integer rdfs:subClassOf bsfs:Literal .
+bsfs:Number rdfs:subClassOf bsfs:Literal .
+xsd:integer rdfs:subClassOf bsfs:Number .
bse:filesize rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
diff --git a/test/apps/test_migrate.py b/test/apps/test_migrate.py
index 957509a..230c032 100644
--- a/test/apps/test_migrate.py
+++ b/test/apps/test_migrate.py
@@ -13,7 +13,7 @@ import unittest
import unittest.mock
# bsie imports
-from bsfs.schema import Schema
+from bsfs import schema
# objects to test
from bsfs.apps.migrate import main
@@ -33,21 +33,21 @@ class TestMigrate(unittest.TestCase):
# read schema from file
with open(schema_1) as ifile:
- target = Schema.from_string(ifile.read())
+ target = schema.from_string(ifile.read())
graph = main([config, schema_1])
self.assertTrue(target <= graph.schema)
# read schema from multiple files
with open(schema_1) as ifile:
- target = Schema.from_string(ifile.read())
+ target = schema.from_string(ifile.read())
with open(schema_2) as ifile:
- target = target + Schema.from_string(ifile.read())
+ target = target + schema.from_string(ifile.read())
graph = main([config, schema_1, schema_2])
self.assertTrue(target <= graph.schema)
# read schema from stdin
with open(schema_1, 'rt') as ifile:
- target = Schema.from_string(ifile.read())
+ target = schema.from_string(ifile.read())
with open(schema_1, 'rt') as ifile:
with unittest.mock.patch('sys.stdin', ifile):
graph = main([config])
diff --git a/test/graph/ac/test_null.py b/test/graph/ac/test_null.py
index c863943..e35852d 100644
--- a/test/graph/ac/test_null.py
+++ b/test/graph/ac/test_null.py
@@ -8,7 +8,7 @@ Author: Matthias Baumgartner, 2022
import unittest
# bsie imports
-from bsfs import schema as _schema
+from bsfs import schema as bsc
from bsfs.namespace import ns
from bsfs.query import ast
from bsfs.triple_store import SparqlStore
@@ -23,7 +23,7 @@ from bsfs.graph.ac.null import NullAC
class TestNullAC(unittest.TestCase):
def setUp(self):
self.backend = SparqlStore()
- self.backend.schema = _schema.Schema.from_string('''
+ self.backend.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
@@ -34,7 +34,8 @@ class TestNullAC(unittest.TestCase):
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
# predicates mandated by Nodes
bsm:t_created rdfs:subClassOf bsfs:Predicate ;
diff --git a/test/graph/test_graph.py b/test/graph/test_graph.py
index 8503d5b..f97783b 100644
--- a/test/graph/test_graph.py
+++ b/test/graph/test_graph.py
@@ -25,7 +25,7 @@ class TestGraph(unittest.TestCase):
def setUp(self):
self.user = URI('http://example.com/me')
self.backend = SparqlStore.Open()
- self.backend.schema = schema.Schema.from_string('''
+ self.backend.schema = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix bsfs: <http://bsfs.ai/schema/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -118,14 +118,15 @@ class TestGraph(unittest.TestCase):
schema.Node(ns.bsfs.Node, None)))}), append=False)
# can migrate to compatible schema
- target_1 = schema.Schema.from_string('''
+ target_1 = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
prefix bsfs: <http://bsfs.ai/schema/>
prefix bse: <http://bsfs.ai/schema/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
bse:filename rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -142,12 +143,13 @@ class TestGraph(unittest.TestCase):
# new schema is applied
self.assertLess(target_1, graph.schema)
# graph appends its predicates
- self.assertEqual(graph.schema, target_1 + schema.Schema.from_string('''
+ self.assertEqual(graph.schema, target_1 + schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
prefix bsfs: <http://bsfs.ai/schema/>
prefix bsm: <http://bsfs.ai/schema/Meta#>
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
bsm:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
rdfs:range xsd:integer ;
@@ -155,14 +157,15 @@ class TestGraph(unittest.TestCase):
'''))
# can overwrite the current schema
- target_2 = schema.Schema.from_string('''
+ target_2 = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
prefix bsfs: <http://bsfs.ai/schema/>
prefix bse: <http://bsfs.ai/schema/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
bse:filename rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -181,12 +184,13 @@ class TestGraph(unittest.TestCase):
# new schema is applied
self.assertLess(target_2, graph.schema)
# graph appends its predicates
- self.assertEqual(graph.schema, target_2 + schema.Schema.from_string('''
+ self.assertEqual(graph.schema, target_2 + schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
prefix bsfs: <http://bsfs.ai/schema/>
prefix bsm: <http://bsfs.ai/schema/Meta#>
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
bsm:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
rdfs:range xsd:integer ;
@@ -196,7 +200,7 @@ class TestGraph(unittest.TestCase):
def test_get(self):
# setup
graph = Graph(self.backend, self.user)
- graph.migrate(schema.Schema.from_string('''
+ graph.migrate(schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
prefix bsfs: <http://bsfs.ai/schema/>
diff --git a/test/graph/test_nodes.py b/test/graph/test_nodes.py
index 11ae46d..2870f35 100644
--- a/test/graph/test_nodes.py
+++ b/test/graph/test_nodes.py
@@ -24,7 +24,7 @@ class TestNodes(unittest.TestCase):
def setUp(self):
# initialize backend
self.backend = SparqlStore()
- self.backend.schema = _schema.Schema.from_string('''
+ self.backend.schema = _schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
@@ -37,7 +37,8 @@ class TestNodes(unittest.TestCase):
bsfs:Tag rdfs:subClassOf bsfs:Node .
bsfs:User rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
# predicates mandated by Nodes
bsm:t_created rdfs:subClassOf bsfs:Predicate ;
@@ -78,7 +79,11 @@ class TestNodes(unittest.TestCase):
(rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.xsd.string), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Array)),
+ (rdflib.URIRef(ns.bsfs.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Number)),
(rdflib.URIRef(ns.bsm.t_created), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.comment), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
diff --git a/test/graph/test_resolve.py b/test/graph/test_resolve.py
index 5bc99e4..0918b02 100644
--- a/test/graph/test_resolve.py
+++ b/test/graph/test_resolve.py
@@ -31,7 +31,7 @@ class TestFilter(unittest.TestCase):
"""
def test_call(self):
- schema = bsc.Schema.from_string('''
+ schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
@@ -41,7 +41,17 @@ class TestFilter(unittest.TestCase):
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ bsfs:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array .
+ xsd:integer rdfs:subClassOf bsfs:Number .
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "5"^^xsd:integer .
+
+ bse:colors rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Colors .
bse:comment rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -65,7 +75,7 @@ class TestFilter(unittest.TestCase):
{URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
tags = graph.nodes(ns.bsfs.Tag,
{URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
- invalid = nodes.Nodes(None, '', schema.node(ns.bsfs.Node).get_child(ns.bsfs.Invalid),
+ invalid = nodes.Nodes(None, '', schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
{'http://example.com/you/invalid#1234', 'http://example.com/you/invalid#4321'})
resolver = Filter(schema)
@@ -144,12 +154,18 @@ class TestFilter(unittest.TestCase):
self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
ast.filter.Has(ns.bse.comment)),
ast.filter.Has(ns.bse.comment))
+ # for sake of completeness: Distance
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.colors, ast.filter.Distance([1,2,3,4,5], 1))),
+ ast.filter.Any(ns.bse.colors, ast.filter.Distance([1,2,3,4,5], 1)))
# route errors
self.assertRaises(errors.BackendError, resolver, schema.node(ns.bsfs.Tag),
ast.filter.Predicate(ns.bse.comment))
self.assertRaises(errors.BackendError, resolver, schema.node(ns.bsfs.Tag),
ast.filter.Any(ast.filter.PredicateExpression(), ast.filter.Equals('foo')))
- self.assertRaises(errors.UnreachableError, resolver._one_of, ast.filter.OneOf(ast.filter.Predicate(ns.bsfs.Predicate)))
+ self.assertRaises(errors.BackendError, resolver._one_of, ast.filter.OneOf(ast.filter.Predicate(ns.bsfs.Predicate)))
+ # for sake of coverage completeness: valid OneOf
+ self.assertIsNotNone(resolver._one_of(ast.filter.OneOf(ast.filter.Predicate(ns.bse.colors))))
# check schema consistency
self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Tag),
diff --git a/test/namespace/test_namespace.py b/test/namespace/test_namespace.py
index f109653..2536203 100644
--- a/test/namespace/test_namespace.py
+++ b/test/namespace/test_namespace.py
@@ -20,15 +20,15 @@ from bsfs.namespace.namespace import Namespace, ClosedNamespace
class TestNamespace(unittest.TestCase):
def test_essentials(self):
# string conversion
- self.assertEqual(str(Namespace('http://example.org/')), 'Namespace(http://example.org)')
- self.assertEqual(str(Namespace('http://example.org#')), 'Namespace(http://example.org)')
+ self.assertEqual(str(Namespace('http://example.org/')), 'http://example.org')
+ self.assertEqual(str(Namespace('http://example.org#')), 'http://example.org')
self.assertEqual(repr(Namespace('http://example.org/')), 'Namespace(http://example.org, #, /)')
self.assertEqual(repr(Namespace('http://example.org#')), 'Namespace(http://example.org, #, /)')
self.assertEqual(repr(Namespace('http://example.org', fsep='.')), 'Namespace(http://example.org, ., /)')
self.assertEqual(repr(Namespace('http://example.org', psep='.')), 'Namespace(http://example.org, #, .)')
# repeated separators are truncated
- self.assertEqual(str(Namespace('http://example.org////')), 'Namespace(http://example.org)')
- self.assertEqual(str(Namespace('http://example.org####')), 'Namespace(http://example.org)')
+ self.assertEqual(str(Namespace('http://example.org////')), 'http://example.org')
+ self.assertEqual(str(Namespace('http://example.org####')), 'http://example.org')
self.assertEqual(repr(Namespace('http://example.org///##')), 'Namespace(http://example.org, #, /)')
# comparison
class Foo(Namespace): pass
@@ -83,8 +83,8 @@ class TestNamespace(unittest.TestCase):
class TestClosedNamespace(unittest.TestCase):
def test_essentials(self):
# string conversion
- self.assertEqual(str(ClosedNamespace('http://example.org/')), 'ClosedNamespace(http://example.org)')
- self.assertEqual(str(ClosedNamespace('http://example.org#')), 'ClosedNamespace(http://example.org)')
+ self.assertEqual(str(ClosedNamespace('http://example.org/')), 'http://example.org')
+ self.assertEqual(str(ClosedNamespace('http://example.org#')), 'http://example.org')
self.assertEqual(repr(ClosedNamespace('http://example.org/')), 'ClosedNamespace(http://example.org, #, /)')
self.assertEqual(repr(ClosedNamespace('http://example.org#')), 'ClosedNamespace(http://example.org, #, /)')
self.assertEqual(repr(ClosedNamespace('http://example.org', fsep='.')), 'ClosedNamespace(http://example.org, ., /)')
diff --git a/test/query/ast/__init__.py b/test/query/ast_test/__init__.py
index e69de29..e69de29 100644
--- a/test/query/ast/__init__.py
+++ b/test/query/ast_test/__init__.py
diff --git a/test/query/ast/test_filter_.py b/test/query/ast_test/test_filter_.py
index 4f69bdc..9eb92e2 100644
--- a/test/query/ast/test_filter_.py
+++ b/test/query/ast_test/test_filter_.py
@@ -15,7 +15,7 @@ from bsfs.utils import URI
from bsfs.query.ast.filter_ import _Expression, FilterExpression, PredicateExpression
from bsfs.query.ast.filter_ import _Branch, Any, All
from bsfs.query.ast.filter_ import _Agg, And, Or
-from bsfs.query.ast.filter_ import Not, Has
+from bsfs.query.ast.filter_ import Not, Has, Distance
from bsfs.query.ast.filter_ import _Value, Is, Equals, Substring, StartsWith, EndsWith
from bsfs.query.ast.filter_ import _Bounded, LessThan, GreaterThan
from bsfs.query.ast.filter_ import Predicate, OneOf
@@ -284,6 +284,39 @@ class TestValue(unittest.TestCase):
self.assertEqual(cls(f).value, f)
+class TestDistance(unittest.TestCase):
+ def test_essentials(self):
+ ref = (1,2,3)
+ # comparison
+ self.assertEqual(Distance(ref, 3), Distance(ref, 3))
+ self.assertEqual(hash(Distance(ref, 3)), hash(Distance(ref, 3)))
+ # comparison respects type
+ self.assertNotEqual(Distance(ref, 3), FilterExpression())
+ self.assertNotEqual(hash(Distance(ref, 3)), hash(FilterExpression()))
+ # comparison respects reference
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2), 3, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2), 3, False)))
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,5,3), 3, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,5,3), 3, False)))
+ # comparison respects threshold
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2,3), 3.1, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2,3), 3.1, False)))
+ # comparison respects strict flag
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2,3), 3, True))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2,3), 3, True)))
+ # string conversion
+ self.assertEqual(str(Distance(ref, 3, False)), 'Distance((1, 2, 3), 3.0, False)')
+ self.assertEqual(repr(Distance(ref, 3, False)), 'Distance((1, 2, 3), 3.0, False)')
+
+ def test_members(self):
+ self.assertEqual(Distance((1,2,3), 3, False).reference, (1,2,3))
+ self.assertEqual(Distance((3,2,1), 3, False).reference, (3,2,1))
+ self.assertEqual(Distance((1,2,3), 3, False).threshold, 3.0)
+ self.assertEqual(Distance((1,2,3), 53.45, False).threshold, 53.45)
+ self.assertEqual(Distance((1,2,3), 3, False).strict, False)
+ self.assertEqual(Distance((1,2,3), 3, True).strict, True)
+
+
class TestBounded(unittest.TestCase):
def test_essentials(self):
# comparison respects type
diff --git a/test/query/test_validator.py b/test/query/test_validator.py
index 4f8364a..dc9d913 100644
--- a/test/query/test_validator.py
+++ b/test/query/test_validator.py
@@ -21,7 +21,7 @@ from bsfs.query.validator import Filter
class TestFilter(unittest.TestCase):
def setUp(self):
- self.schema = _schema.Schema.from_string('''
+ self.schema = _schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
@@ -33,7 +33,19 @@ class TestFilter(unittest.TestCase):
bsfs:Tag rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ bsfs:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array .
+ xsd:integer rdfs:subClassOf bsfs:Number .
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "5"^^xsd:integer ;
+ bsfs:dtype bsfs:f32 .
+
+ bse:color rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Colors ;
+ bsfs:unique "true"^^xsd:boolean .
bse:comment rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
@@ -69,8 +81,8 @@ class TestFilter(unittest.TestCase):
self.assertRaises(TypeError, self.validate, '1234', None)
self.assertRaises(TypeError, self.validate, self.schema.literal(ns.bsfs.URI), None)
# root_type must exist in the schema
- self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Node).get_child(ns.bsfs.Image), None)
- self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity).get_child(ns.bsfs.Image), None)
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Image), None)
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity).child(ns.bsfs.Image), None)
# valid query returns true
self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
ast.filter.Any(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy),
@@ -85,6 +97,7 @@ class TestFilter(unittest.TestCase):
),
ast.filter.Not(ast.filter.Any(ns.bse.comment,
ast.filter.Not(ast.filter.Equals('hello world')))),
+ ast.filter.Any(ns.bse.color, ast.filter.Distance([1,2,3,4,5], 3)),
)))))
# invalid paths raise consistency error
self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity),
@@ -130,7 +143,7 @@ class TestFilter(unittest.TestCase):
# type must be a node
self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal), None)
# type must be in the schema
- self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).get_child(ns.bsfs.Invalid), None)
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), None)
# predicate is verified
self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
ast.filter.Any(ns.bsfs.Invalid, ast.filter.Is('http://example.com/entity#1234')))
@@ -187,7 +200,7 @@ class TestFilter(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.literal(ns.bsfs.Literal),
ast.filter.Has(ns.bse.tag))
# type must be in the schema
- self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Node).get_child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
ast.filter.Has(ns.bse.tag))
# has checks predicate
self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Entity),
@@ -206,7 +219,7 @@ class TestFilter(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, self.validate._is, self.schema.literal(ns.bsfs.Literal),
ast.filter.Is('http://example.com/foo'))
# type must be in the schema
- self.assertRaises(errors.ConsistencyError, self.validate._is, self.schema.node(ns.bsfs.Node).get_child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._is, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
ast.filter.Is('http://example.com/foo'))
# is accepts correct expressions
self.assertIsNone(self.validate._is(self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/entity#1234')))
@@ -222,13 +235,13 @@ class TestFilter(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
ast.filter.EndsWith('hello world'))
# type must be in the schema
- self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).get_child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
ast.filter.Equals('hello world'))
- self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).get_child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
ast.filter.Substring('hello world'))
- self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).get_child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
ast.filter.StartsWith('hello world'))
- self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).get_child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
ast.filter.EndsWith('hello world'))
# value accepts correct expressions
self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.Equals('hello world')))
@@ -243,14 +256,34 @@ class TestFilter(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.node(ns.bsfs.Node),
ast.filter.LessThan(0))
# type must be in the schema
- self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.bsfs.Literal).get_child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
ast.filter.GreaterThan(0))
- self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.bsfs.Literal).get_child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.LessThan(0))
+ # type must be a number
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.xsd.string),
ast.filter.LessThan(0))
# bounded accepts correct expressions
self.assertIsNone(self.validate._bounded(self.schema.literal(ns.xsd.integer), ast.filter.LessThan(0)))
self.assertIsNone(self.validate._bounded(self.schema.literal(ns.xsd.integer), ast.filter.GreaterThan(0)))
+ def test_distance(self):
+ # type must be a literal
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.node(ns.bsfs.Node),
+ ast.filter.Distance([1,2,3], 1, False))
+ # type must be a feature
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsfs.Array),
+ ast.filter.Distance([1,2,3], 1, False))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsfs.Feature).child(ns.bsfs.Invalid),
+ ast.filter.Distance([1,2,3], 1, False))
+ # FIXME: reference must be a numpy array
+ # reference must have the correct dimension
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsfs.Colors),
+ ast.filter.Distance([1,2,3], 1, False))
+ # FIXME: reference must have the correct dtype
+ # distance accepts correct expressions
+ self.assertIsNone(self.validate._distance(self.schema.literal(ns.bsfs.Colors), ast.filter.Distance([1,2,3,4,5], 1, False)))
## main ##
diff --git a/test/schema/test_schema.py b/test/schema/test_schema.py
index 888cdca..32dbc93 100644
--- a/test/schema/test_schema.py
+++ b/test/schema/test_schema.py
@@ -10,7 +10,7 @@ import unittest
# bsfs imports
from bsfs.namespace import ns
-from bsfs.schema import types
+from bsfs.schema import types, from_string
from bsfs.utils import errors
# objects to test
@@ -35,7 +35,8 @@ class TestSchema(unittest.TestCase):
bsfs:Unused rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
xsd:boolean rdfs:subClassOf bsfs:Literal .
bse:tag rdfs:subClassOf bsfs:Predicate ;
@@ -55,32 +56,42 @@ class TestSchema(unittest.TestCase):
'''
# nodes
- self.n_root = types.Node(ns.bsfs.Node, None)
- self.n_ent = types.Node(ns.bsfs.Entity, types.Node(ns.bsfs.Node, None))
- self.n_img = types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Entity, types.Node(ns.bsfs.Node, None)))
- self.n_tag = types.Node(ns.bsfs.Tag, types.Node(ns.bsfs.Node, None))
- self.n_unused = types.Node(ns.bsfs.Unused, types.Node(ns.bsfs.Node, None))
+ self.n_root = types.ROOT_NODE
+ self.n_ent = self.n_root.child(ns.bsfs.Entity)
+ self.n_img = self.n_ent.child(ns.bsfs.Image)
+ self.n_tag = self.n_root.child(ns.bsfs.Tag)
+ self.n_unused = self.n_root.child(ns.bsfs.Unused)
self.nodes = [self.n_root, self.n_ent, self.n_img, self.n_tag, self.n_unused]
# literals
- self.l_root = types.Literal(ns.bsfs.Literal, None)
- self.l_string = types.Literal(ns.xsd.string, types.Literal(ns.bsfs.Literal, None))
- self.l_integer = types.Literal(ns.xsd.integer, types.Literal(ns.bsfs.Literal, None))
- self.l_unused = types.Literal(ns.xsd.boolean, types.Literal(ns.bsfs.Literal, None))
- self.literals = [self.l_root, self.l_string, self.l_integer, self.l_unused]
+ self.l_root = types.ROOT_LITERAL
+ self.l_number = types.ROOT_NUMBER
+ self.l_array = types.ROOT_ARRAY
+ self.l_time = types.ROOT_TIME
+ self.l_string = self.l_root.child(ns.xsd.string)
+ self.l_integer = self.l_root.child(ns.xsd.integer)
+ self.l_unused = self.l_root.child(ns.xsd.boolean)
+ self.f_root = types.ROOT_FEATURE
+ self.literals = [self.l_root, self.l_array, self.f_root, self.l_number, self.l_time, self.l_string, self.l_integer, self.l_unused]
# predicates
- self.p_root = types.Predicate(ns.bsfs.Predicate, None, types.Node(ns.bsfs.Node, None), None, False)
- self.p_tag = self.p_root.get_child(ns.bse.tag, self.n_ent, self.n_tag, False)
- self.p_group = self.p_tag.get_child(ns.bse.group, self.n_img, self.n_tag, False)
- self.p_comment = self.p_root.get_child(ns.bse.comment, self.n_root, self.l_string, True)
+ self.p_root = types.ROOT_PREDICATE
+ self.p_tag = self.p_root.child(ns.bse.tag, self.n_ent, self.n_tag, False)
+ self.p_group = self.p_tag.child(ns.bse.group, self.n_img, self.n_tag, False)
+ self.p_comment = self.p_root.child(ns.bse.comment, self.n_root, self.l_string, True)
self.predicates = [self.p_root, self.p_tag, self.p_group, self.p_comment]
def test_construction(self):
+ # no args yields a minimal schema
+ schema = Schema()
+ self.assertSetEqual(set(schema.nodes()), {self.n_root})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_number, self.l_array, self.l_time, self.f_root})
+ self.assertSetEqual(set(schema.predicates()), {self.p_root})
+
# nodes and literals are optional
schema = Schema(self.predicates)
self.assertSetEqual(set(schema.nodes()), {self.n_root, self.n_ent, self.n_img, self.n_tag})
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number, self.l_time, self.l_array, self.f_root})
self.assertSetEqual(set(schema.predicates()), set(self.predicates))
# predicates, nodes, and literals are respected
@@ -101,19 +112,19 @@ class TestSchema(unittest.TestCase):
# literals are complete
schema = Schema(self.predicates, self.nodes, None)
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number, self.l_array, self.l_time, self.f_root})
schema = Schema(self.predicates, self.nodes, [])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number, self.l_array, self.l_time, self.f_root})
schema = Schema(self.predicates, self.nodes, [self.l_string])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number, self.l_array, self.l_time, self.f_root})
schema = Schema(self.predicates, self.nodes, [self.l_integer])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_integer})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_integer, self.l_number, self.l_array, self.l_time, self.f_root})
schema = Schema(self.predicates, self.nodes, [self.l_integer, self.l_unused])
self.assertSetEqual(set(schema.literals()), set(self.literals))
# predicates are complete
schema = Schema([], self.nodes, self.literals)
- self.assertSetEqual(set(schema.predicates()), set())
+ self.assertSetEqual(set(schema.predicates()), {self.p_root})
schema = Schema([self.p_group], self.nodes, self.literals)
self.assertSetEqual(set(schema.predicates()), {self.p_root, self.p_tag, self.p_group})
schema = Schema([self.p_group, self.p_comment], self.nodes, self.literals)
@@ -153,20 +164,27 @@ class TestSchema(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, Schema,
{}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {}, {types.Node(ns.bsfs.Foo, None)})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {}, {types.Node(ns.bsfs.Foo, None)})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {types.Node(ns.bsfs.Foo, None)}, {})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {types.Node(ns.bsfs.Foo, None)}, {})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
+
def test_str(self):
+ # string conversion
self.assertEqual(str(Schema([])), 'Schema()')
self.assertEqual(str(Schema([], [], [])), 'Schema()')
self.assertEqual(str(Schema(self.predicates, self.nodes, self.literals)), 'Schema()')
- self.assertEqual(repr(Schema([])), 'Schema([], [], [])')
- self.assertEqual(repr(Schema([], [], [])), 'Schema([], [], [])')
+ # repr conversion with only default nodes, literals, and predicates
+ n = [ns.bsfs.Node]
+ l = [ns.bsfs.Array, ns.bsfs.Feature, ns.bsfs.Literal, ns.bsfs.Number, ns.bsfs.Time]
+ p = [ns.bsfs.Predicate]
+ self.assertEqual(repr(Schema()), f'Schema({n}, {l}, {p})')
+ self.assertEqual(repr(Schema([], [], [])), f'Schema({n}, {l}, {p})')
+ # repr conversion
n = [ns.bsfs.Entity, ns.bsfs.Image, ns.bsfs.Node, ns.bsfs.Tag, ns.bsfs.Unused]
- l = [ns.bsfs.Literal, ns.xsd.boolean, ns.xsd.integer, ns.xsd.string]
+ l = [ns.bsfs.Array, ns.bsfs.Feature, ns.bsfs.Literal, ns.bsfs.Number, ns.bsfs.Time, ns.xsd.boolean, ns.xsd.integer, ns.xsd.string]
p = [ns.bse.comment, ns.bse.group, ns.bse.tag, ns.bsfs.Predicate]
self.assertEqual(repr(Schema(self.predicates, self.nodes, self.literals)), f'Schema({n}, {l}, {p})')
@@ -202,16 +220,16 @@ class TestSchema(unittest.TestCase):
self.assertNotEqual(hash(schema),
hash(Schema([self.p_group, self.p_tag, self.p_root], self.nodes, self.literals)))
self.assertNotEqual(schema,
- Schema(self.predicates + [self.p_root.get_child(ns.bse.filesize, self.n_ent, self.l_integer)], self.nodes, self.literals))
+ Schema(self.predicates + [self.p_root.child(ns.bse.filesize, self.n_ent, self.l_integer)], self.nodes, self.literals))
self.assertNotEqual(hash(schema),
- hash(Schema(self.predicates + [self.p_root.get_child(ns.bse.filesize, self.n_ent, self.l_integer)], self.nodes, self.literals)))
+ hash(Schema(self.predicates + [self.p_root.child(ns.bse.filesize, self.n_ent, self.l_integer)], self.nodes, self.literals)))
def test_order(self):
# setup
class Foo(): pass
- p_foo = self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, True)
- p_sub = p_foo.get_child(ns.bse.sub, self.n_ent, self.l_string, True)
- p_bar = self.p_root.get_child(ns.bse.bar, self.n_ent, self.l_string, True)
+ p_foo = self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, True)
+ p_sub = p_foo.child(ns.bse.sub, self.n_ent, self.l_string, True)
+ p_bar = self.p_root.child(ns.bse.bar, self.n_ent, self.l_string, True)
# can only compare schema to other schema
# <
@@ -258,11 +276,11 @@ class TestSchema(unittest.TestCase):
self.assertTrue(operator.lt(Schema({self.p_tag}), Schema({self.p_group})))
self.assertTrue(operator.le(Schema({self.p_tag}), Schema({self.p_group})))
# subset considers differences in predicates and literals
- self.assertTrue(operator.lt(Schema.Empty(), Schema({self.p_comment})))
+ self.assertTrue(operator.lt(Schema(), Schema({self.p_comment})))
# subset considers differences in predicates, nodes, and literals
- self.assertTrue(operator.lt(Schema({}), Schema.Empty()))
- self.assertTrue(operator.lt(Schema({self.p_tag}), Schema.from_string(self.schema_str)))
- self.assertTrue(operator.le(Schema({self.p_tag}), Schema.from_string(self.schema_str)))
+ self.assertTrue(operator.le(Schema({}), Schema()))
+ self.assertTrue(operator.lt(Schema({self.p_tag}), from_string(self.schema_str)))
+ self.assertTrue(operator.le(Schema({self.p_tag}), from_string(self.schema_str)))
self.assertFalse(operator.lt(Schema({self.p_comment}), Schema({self.p_tag})))
self.assertFalse(operator.le(Schema({self.p_comment}), Schema({self.p_tag})))
@@ -280,54 +298,54 @@ class TestSchema(unittest.TestCase):
self.assertTrue(operator.gt(Schema({self.p_group}), Schema({self.p_tag})))
self.assertTrue(operator.ge(Schema({self.p_group}), Schema({self.p_tag})))
# superset considers differences in predicates and literals
- self.assertTrue(operator.gt(Schema({self.p_comment}), Schema.Empty()))
+ self.assertTrue(operator.gt(Schema({self.p_comment}), Schema()))
# superset considers differences in predicates, nodes, and literals
- self.assertTrue(operator.gt(Schema.Empty(), Schema({})))
- self.assertTrue(operator.gt(Schema.from_string(self.schema_str), Schema({self.p_tag})))
- self.assertTrue(operator.ge(Schema.from_string(self.schema_str), Schema({self.p_tag})))
+ self.assertTrue(operator.ge(Schema(), Schema({})))
+ self.assertTrue(operator.gt(from_string(self.schema_str), Schema({self.p_tag})))
+ self.assertTrue(operator.ge(from_string(self.schema_str), Schema({self.p_tag})))
self.assertFalse(operator.gt(Schema({self.p_tag}), Schema({self.p_comment})))
self.assertFalse(operator.ge(Schema({self.p_tag}), Schema({self.p_comment})))
# inconsistent schema cannot be a subset
self.assertFalse(operator.le(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
self.assertFalse(operator.le(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
+ self.p_root.child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
self.assertFalse(operator.le(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
self.assertFalse(operator.le(Schema({}, {self.n_img}), Schema({}, {
types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Node, None))})))
self.assertFalse(operator.le(Schema({}, {}, {self.l_integer}), Schema({}, {}, {
types.Literal(ns.xsd.integer, types.Literal(ns.xsd.number, types.Literal(ns.bsfs.Literal, None)))})))
# inconsistent schema cannot be a true subset
self.assertFalse(operator.lt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
self.assertFalse(operator.lt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
+ self.p_root.child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
self.assertFalse(operator.lt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
self.assertFalse(operator.lt(Schema({}, {self.n_img}), Schema({}, {
types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Node, None))})))
self.assertFalse(operator.lt(Schema({}, {}, {self.l_integer}), Schema({}, {}, {
types.Literal(ns.xsd.integer, types.Literal(ns.xsd.number, types.Literal(ns.bsfs.Literal, None)))})))
# inconsistent schema cannot be a superset
self.assertFalse(operator.ge(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
self.assertFalse(operator.ge(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
+ self.p_root.child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
self.assertFalse(operator.ge(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
self.assertFalse(operator.ge(Schema({}, {self.n_img}), Schema({}, {
types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Node, None))})))
self.assertFalse(operator.ge(Schema({}, {}, {self.l_integer}), Schema({}, {}, {
types.Literal(ns.xsd.integer, types.Literal(ns.xsd.number, types.Literal(ns.bsfs.Literal, None)))})))
# inconsistent schema cannot be a true superset
self.assertFalse(operator.gt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
self.assertFalse(operator.gt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
+ self.p_root.child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
self.assertFalse(operator.gt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
self.assertFalse(operator.gt(Schema({}, {self.n_img}), Schema({}, {
types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Node, None))})))
self.assertFalse(operator.gt(Schema({}, {}, {self.l_integer}), Schema({}, {}, {
@@ -351,26 +369,26 @@ class TestSchema(unittest.TestCase):
# difference does not contain predicates from the RHS
diff = Schema({self.p_tag, self.p_comment}).diff(Schema({self.p_group}))
self.assertSetEqual(set(diff.nodes), set())
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_string})
+ self.assertSetEqual(set(diff.literals), {self.l_string})
self.assertSetEqual(set(diff.predicates), {self.p_comment})
# difference considers extra nodes and literals
diff = Schema({self.p_tag}, {self.n_unused}, {self.l_unused}).diff(Schema({self.p_tag}))
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_unused})
self.assertSetEqual(set(diff.predicates), set())
# difference considers inconsistent types
diff = Schema({self.p_tag}, {self.n_unused}, {self.l_unused}).diff(
Schema({self.p_tag}, {types.Node(ns.bsfs.Unused, None)}, {types.Literal(ns.xsd.boolean, None)}))
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_unused})
self.assertSetEqual(set(diff.predicates), set())
# __sub__ is an alias for diff
diff = Schema({self.p_comment}, {self.n_unused}, {self.l_unused}) - Schema({self.p_group})
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_string, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_string, self.l_unused})
self.assertSetEqual(set(diff.predicates), {self.p_comment})
# __sub__ only accepts Schema instances
class Foo(): pass
@@ -547,196 +565,6 @@ class TestSchema(unittest.TestCase):
self.assertFalse(schema.has_predicate(ns.bse.mimetype))
self.assertFalse(schema.has_predicate(self.p_root))
- def test_empty(self):
- self.assertEqual(Schema.Empty(), Schema(
- [types.Predicate(ns.bsfs.Predicate, None, types.Node(ns.bsfs.Node, None), None, False)],
- [types.Node(ns.bsfs.Node, None)],
- [types.Literal(ns.bsfs.Literal, None)],
- ))
-
- def test_from_string(self):
- # from_string creates a schema
- self.assertEqual(
- Schema(self.predicates, self.nodes, self.literals),
- Schema.from_string(self.schema_str))
-
- # schema contains at least the root types
- self.assertEqual(Schema.from_string(''), Schema({self.p_root}, {self.n_root}, {self.l_root}))
-
- # custom example
- self.assertEqual(
- Schema({types.Predicate(ns.bsfs.Predicate, None, self.n_root, None, False).get_child(
- ns.bse.filename, self.n_ent, self.l_string, False)}),
- Schema.from_string('''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- xsd:string rdfs:subClassOf bsfs:Literal .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- '''))
-
- # all nodes must be defined
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- xsd:string rdfs:subClassOf bsfs:Literal .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # all literals must be defined
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # must not have circular dependencies
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- # ah, a nice circular dependency
- bsfs:Entity rdfs:subClassOf bsfs:Document .
- bsfs:Document rdfs:subClassOf bsfs:Entity .
- bsfs:PDF rdfs:subClassOf bsfs:Document .
- ''')
-
- # range must be a node or literal
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range bsfs:Foo ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range bsfs:Predicate ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # must be consistent
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- bsfs:Document rdfs:subClassOf bsfs:Node .
- bsfs:Document rdfs:subClassOf bsfs:Entity.
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
-
- xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:name rdfs:subClassOf bsfs:Literal .
- xsd:name rdfs:subClassOf xsd:string .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity .
-
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:range bsfs:Entity .
-
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- bsfs:unique "true"^^xsd:boolean .
-
- ''')
-
-
-
## main ##
if __name__ == '__main__':
diff --git a/test/schema/test_serialize.py b/test/schema/test_serialize.py
new file mode 100644
index 0000000..fc6b20a
--- /dev/null
+++ b/test/schema/test_serialize.py
@@ -0,0 +1,1030 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# 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 ##
+
+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.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.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: <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.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: <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.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.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: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ # literals inherit from same parent
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs: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: <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.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: <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.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: <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.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: <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.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: <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.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: <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.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: <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):
+ # 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: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature .
+
+ '''))
+
+ # features inherit properties from parents
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.i32)
+ f_main_colors = f_colors.child(ns.bsfs.MainColor, distance=ns.bsfs.cosine, dtype=ns.bsfs.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: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ; # inherits distance from bsfs:Feature
+ bsfs:dimension "1234"^^xsd:integer ; # overwrites bsfs:Feature
+ bsfs:dtype bsfs:i32 . # overwrites bsfs:Feature
+
+ bsfs:MainColor rdfs:subClassOf bsfs:Colors ; # inherits dimension from bsfs:Colors
+ bsfs:distance bsfs:cosine ; # overwrites bsfs:Feature
+ bsfs:dtype bsfs: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.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: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "1234"^^xsd:integer ; # non-conflicting repetition
+ 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:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsfs:ColorSpace rdfs:subClassOf bsfs:Feature .
+
+ bsfs:Colors rdfs:subClassOf bsfs: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: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ bsfs:Colors 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:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dtype bsfs:f32 .
+
+ bsfs:Colors 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:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:distance bsfs:euclidean .
+
+ bsfs:Colors 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:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+
+ bsfs:Colors rdfs:subClassOf bsfs: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: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+
+ bsfs:Colors rdfs:subClassOf bsfs: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('http://bsfs.ai/schema/Feature/colors_spatial'),
+ dtype=ns.bsfs.f16, distance=ns.bsfs.euclidean)
+ f_colors1234 = f_colors.child(URI('http://bsfs.ai/schema/Feature/colors_spatial#1234'), dimension=1024)
+ f_colors4321 = f_colors.child(URI('http://bsfs.ai/schema/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: <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 .
+ bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+
+
+ # abstract predicates
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate ;
+ rdfs:label "node annotation"^^xsd:string .
+
+ # feature instances
+ <http://bsfs.ai/schema/Feature/colors_spatial> rdfs:subClassOf bsfs:Feature ;
+ 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.literal(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.literal(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'})
+
+ # blank nodes result in an error
+ self.assertRaises(errors.BackendError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ 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('http://bsfs.ai/schema/Feature/colors'),
+ distance=ns.bsfs.cosine)
+ # derived features
+ f_colors1234 = f_colors.child(URI('http://bsfs.ai/schema/Feature/colors#1234'),
+ dimension=1024) # inherits dtype, distance
+ f_colors4321 = f_colors.child(URI('http://bsfs.ai/schema/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('bsfs:Array', schema_str)
+ self.assertIn('<http://bsfs.ai/schema/Feature/colors', schema_str)
+ self.assertIn('<http://bsfs.ai/schema/Feature/colors#1234', schema_str)
+ self.assertIn('<http://bsfs.ai/schema/Feature/colors#4321', schema_str)
+ # inherited properties are not serialized
+ self.assertIsNotNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#1234>[^\.]*bsfs:dimension[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#1234>[^\.]*bsfs:dtype[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#1234>[^\.]*bsfs:distance[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#4321>[^\.]*bsfs:dimension[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#4321>[^\.]*bsfs:distance[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<http://bsfs\.ai/schema/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('http://bsfs.ai/schema/Feature/colors'),
+ dtype=ns.bsfs.f16, distance=ns.bsfs.euclidean,
+ **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema(literals={f_colors}))).literal(URI('http://bsfs.ai/schema/Feature/colors')).annotations)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/schema/test_types.py b/test/schema/test_types.py
index 4a49e6e..c5895d2 100644
--- a/test/schema/test_types.py
+++ b/test/schema/test_types.py
@@ -10,15 +10,17 @@ import unittest
# bsfs imports
from bsfs.namespace import ns
+from bsfs.schema.types import ROOT_PREDICATE, ROOT_VERTEX, ROOT_FEATURE
from bsfs.utils import errors
# objects to test
-from bsfs.schema.types import _Type, _Vertex, Node, Literal, Predicate
+from bsfs.schema.types import _Type, Vertex, Node, Literal, Predicate, Feature
## code ##
class TestType(unittest.TestCase):
+
def test_parents(self):
# create some types
fst = _Type('First')
@@ -31,7 +33,25 @@ class TestType(unittest.TestCase):
self.assertListEqual(list(trd.parents()), [snd, fst])
self.assertListEqual(list(frd.parents()), [trd, snd, fst])
- def test_essentials(self):
+ def test_annotations(self):
+ # annotations can be empty
+ self.assertDictEqual(_Type('Foo', None).annotations, {})
+ # annotations are stored
+ self.assertDictEqual(_Type('Foo', None, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123})
+ # comparison ignores annotations
+ self.assertEqual(
+ _Type('Foo', None, foo='bar', bar='foo'),
+ _Type('Foo', None, hello='world', foobar=1234))
+ self.assertEqual(
+ hash(_Type('Foo', None, foo='bar', bar='foo')),
+ hash(_Type('Foo', None, hello='world', foobar=1234)))
+ # annotations can be passed to child
+ self.assertDictEqual(_Type('First', foo='bar').child('Second', bar='foo').annotations, {
+ 'bar': 'foo'})
+
+ def test_string_conversion(self):
# type w/o parent
self.assertEqual(str(_Type('Foo')), '_Type(Foo)')
self.assertEqual(repr(_Type('Foo')), '_Type(Foo, None)')
@@ -51,14 +71,17 @@ class TestType(unittest.TestCase):
self.assertEqual(str(_Type('Foo', SubType('Bar'))), '_Type(Foo)')
self.assertEqual(repr(_Type('Foo', SubType('Bar'))), '_Type(Foo, SubType(Bar, None))')
- def test_get_child(self):
+ def test_child(self):
# callee is used as parent
- self.assertEqual(_Type('First').get_child('Second'), _Type('Second', _Type('First')))
+ self.assertEqual(_Type('First').child('Second'), _Type('Second', _Type('First')))
# works with multiple parents
- self.assertEqual(_Type('First').get_child('Second').get_child('Third'), _Type('Third', _Type('Second', _Type('First'))))
+ self.assertEqual(_Type('First').child('Second').child('Third'), _Type('Third', _Type('Second', _Type('First'))))
# type persists
class Foo(_Type): pass
- self.assertEqual(Foo('First').get_child('Second'), Foo('Second', Foo('First')))
+ self.assertEqual(Foo('First').child('Second'), Foo('Second', Foo('First')))
+ # annotations are respected
+ self.assertDictEqual(_Type('First', foo='bar').child('Second', bar='foo').annotations, {
+ 'bar': 'foo'})
def test_equality(self):
# equality depends on uri
@@ -76,6 +99,13 @@ class TestType(unittest.TestCase):
# comparison respects parent
self.assertNotEqual(_Type('Foo', _Type('Bar')), _Type('Foo'))
self.assertNotEqual(hash(_Type('Foo', _Type('Bar'))), hash(_Type('Foo')))
+ # comparison ignores annotations
+ self.assertEqual(
+ _Type('Foo', None, foo='bar', bar='foo'),
+ _Type('Foo', None, hello='world', foobar=1234))
+ self.assertEqual(
+ hash(_Type('Foo', None, foo='bar', bar='foo')),
+ hash(_Type('Foo', None, hello='world', foobar=1234)))
def test_order(self):
# create some types.
@@ -109,27 +139,43 @@ class TestType(unittest.TestCase):
self.assertFalse(bike > bicycle)
self.assertFalse(bike >= bicycle)
self.assertFalse(bike == bicycle)
+
+ # comparing different classes returns False ...
+ # ... when classes are hierarchically related
class Foo(_Type): pass
- foo = Foo(bike.uri, bike.parent)
- # cannot compare different types
- self.assertRaises(TypeError, operator.lt, foo, bike)
- self.assertRaises(TypeError, operator.le, foo, bike)
- self.assertRaises(TypeError, operator.gt, foo, bike)
- self.assertRaises(TypeError, operator.ge, foo, bike)
+ foo = Foo('Foo', bike)
+ self.assertFalse(foo < bike)
+ self.assertFalse(foo <= bike)
+ self.assertFalse(foo > bike)
+ self.assertFalse(foo >= bike)
+ # goes both ways
+ self.assertFalse(bike < foo)
+ self.assertFalse(bike <= foo)
+ self.assertFalse(bike > foo)
+ self.assertFalse(bike >= foo)
+ # ... when classes are unrelated
+ class Bar(_Type): pass
+ bar = Bar('Bar', bike)
+ self.assertFalse(foo < bar)
+ self.assertFalse(foo <= bar)
+ self.assertFalse(foo > bar)
+ self.assertFalse(foo >= bar)
# goes both ways
- self.assertRaises(TypeError, operator.lt, bike, foo)
- self.assertRaises(TypeError, operator.le, bike, foo)
- self.assertRaises(TypeError, operator.gt, bike, foo)
- self.assertRaises(TypeError, operator.ge, bike, foo)
+ self.assertFalse(bar < foo)
+ self.assertFalse(bar <= foo)
+ self.assertFalse(bar > foo)
+ self.assertFalse(bar >= foo)
+
class TestPredicate(unittest.TestCase):
def test_construction(self):
# domain must be a node
self.assertRaises(TypeError, Predicate, ns.bse.foo, 1234, None, True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Literal(ns.bsfs.Foo, None), None, True)
- # range must be None, a Literal, or a Node
+ # range must be a Literal, a Node, or the root Vertex
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), None, True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), 1234, True)
- self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), _Vertex(ns.bsfs.Foo, None), True)
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), Vertex(ns.bsfs.Foo, None), True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), _Type(ns.bsfs.Foo, None), True)
class Foo(): pass
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), Foo(), True)
@@ -138,82 +184,160 @@ class TestPredicate(unittest.TestCase):
n_root = Node(ns.bsfs.Node, None)
n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
n_tag = Node(ns.bsfs.Tag, Node(ns.bsfs.Tag, None))
- root = Predicate(
- uri=ns.bsfs.Predicate,
- parent=None,
+ root = ROOT_PREDICATE
+ tag = Predicate(
+ uri=ns.bse.tag,
+ parent=root,
domain=n_root,
- range=None,
+ range=n_tag,
unique=False,
)
# instance is equal to itself
- self.assertEqual(root, root)
- self.assertEqual(hash(root), hash(root))
+ self.assertEqual(tag, tag)
+ self.assertEqual(hash(tag), hash(tag))
# instance is equal to a clone
- self.assertEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, None, False))
- self.assertEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, None, False)))
+ self.assertEqual(tag, Predicate(ns.bse.tag, root, n_root, n_tag, False))
+ self.assertEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_tag, False)))
# equality respects uri
- self.assertNotEqual(root, Predicate(ns.bsfs.Alternative, None, n_root, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Alternative, None, n_root, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bsfs.Alternative, root, n_root, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bsfs.Alternative, root, n_root, n_tag, False)))
# equality respects parent
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, n_root, n_root, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, n_root, n_root, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, n_root, n_root, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, n_root, n_root, n_tag, False)))
# equality respects domain
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_ent, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_ent, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_ent, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_ent, n_tag, False)))
# equality respects range
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, n_root, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, n_root, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_root, n_root, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_root, False)))
# equality respects unique
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, None, True))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, None, True)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_root, n_tag, True))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_tag, True)))
- def test_get_child(self):
+ def test_child(self):
n_root = Node(ns.bsfs.Node, None)
+ l_root = Literal(ns.bsfs.Literal, None)
n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
n_tag = Node(ns.bsfs.Tag, Node(ns.bsfs.Tag, None))
- root = Predicate(
- uri=ns.bsfs.Predicate,
- parent=None,
- domain=n_root,
- range=None,
- unique=False,
- )
+ root = ROOT_PREDICATE
tag = Predicate(
- uri=ns.bsfs.Entity,
+ uri=ns.bse.tag,
parent=root,
domain=n_ent,
range=n_tag,
unique=False,
)
+ # child returns Predicate
+ self.assertIsInstance(tag.child(ns.bse.foo), Predicate)
# uri is respected
- self.assertEqual(ns.bse.foo, tag.get_child(ns.bse.foo).uri)
+ self.assertEqual(ns.bse.foo, tag.child(ns.bse.foo).uri)
# domain is respected
dom = Node(ns.bsfs.Image, n_ent)
- self.assertEqual(dom, tag.get_child(ns.bse.foo, domain=dom).domain)
+ self.assertEqual(dom, tag.child(ns.bse.foo, domain=dom).domain)
# range is respected
rng = Node(ns.bsfs.Group, n_tag)
- self.assertEqual(rng, tag.get_child(ns.bse.foo, range=rng).range)
+ self.assertEqual(rng, tag.child(ns.bse.foo, range=rng).range)
# cannot set range to None
- self.assertEqual(n_tag, tag.get_child(ns.bse.foo, range=None).range)
+ self.assertEqual(n_tag, tag.child(ns.bse.foo, range=None).range)
# unique is respected
- self.assertTrue(tag.get_child(ns.bse.foo, unique=True).unique)
+ self.assertTrue(tag.child(ns.bse.foo, unique=True).unique)
+ # annotations are respected
+ self.assertDictEqual(tag.child(ns.bse.foo, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123,
+ })
# domain is inherited from parent
- self.assertEqual(n_ent, tag.get_child(ns.bse.foo).domain)
+ self.assertEqual(n_root, root.child(ns.bse.foo).domain)
+ self.assertEqual(n_ent, tag.child(ns.bse.foo).domain)
# range is inherited from parent
- self.assertEqual(n_tag, tag.get_child(ns.bse.foo).range)
+ self.assertEqual(ROOT_VERTEX, root.child(ns.bse.foo).range)
+ self.assertEqual(n_tag, tag.child(ns.bse.foo).range)
# uniqueness is inherited from parent
- self.assertFalse(tag.get_child(ns.bse.foo).unique)
+ self.assertFalse(tag.child(ns.bse.foo).unique)
# domain must be subtype of parent's domain
- self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, domain=n_root)
- self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, domain=Node(ns.bsfs.Image, n_root))
- # range cannot be None
- self.assertRaises(ValueError, root.get_child, ns.bse.foo)
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, domain=n_root)
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, domain=Node(ns.bsfs.Image, n_root))
# range must be subtype of parent's range
- self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, range=n_root)
- self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, range=Node(ns.bsfs.Image, n_root))
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, range=n_root)
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, range=Node(ns.bsfs.Image, n_root))
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, range=Literal(ns.bsfs.Tag, l_root))
+ # range can be subtyped from ROOT_VERTEX to Node or Literal
+ self.assertEqual(n_root, root.child(ns.bse.foo, range=n_root).range)
+ self.assertEqual(l_root, root.child(ns.bse.foo, range=l_root).range)
+
+
+class TestFeature(unittest.TestCase):
+ def test_construction(self):
+ n_root = Node(ns.bsfs.Node, None)
+ l_root = Literal(ns.bsfs.Literal, None)
+ # dimension, dtype, and distance are respected
+ feat = Feature(ns.bsfs.Feature, None, 1234, ns.bsfs.float, ns.bsfs.euclidean)
+ self.assertEqual(1234, feat.dimension)
+ self.assertEqual(ns.bsfs.float, feat.dtype)
+ self.assertEqual(ns.bsfs.euclidean, feat.distance)
+
+ def test_equality(self):
+ n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
+ colors = Feature(
+ uri=ns.bse.colors,
+ parent=ROOT_FEATURE,
+ dimension=1234,
+ dtype=ns.bsfs.float,
+ distance=ns.bsfs.euclidean,
+ )
+ # instance is equal to itself
+ self.assertEqual(colors, colors)
+ self.assertEqual(hash(colors), hash(colors))
+ # instance is equal to a clone
+ self.assertEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.float, ns.bsfs.euclidean))
+ self.assertEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.float, ns.bsfs.euclidean)))
+ # equality respects dimension
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, 4321, ns.bsfs.float, ns.bsfs.euclidean))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, 4321, ns.bsfs.float, ns.bsfs.euclidean)))
+ # equality respects dtype
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.integer, ns.bsfs.euclidean))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.integer, ns.bsfs.euclidean)))
+ # equality respects distance
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.float, ns.bsfs.cosine))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.float, ns.bsfs.cosine)))
+
+ def test_child(self):
+ n_root = Node(ns.bsfs.Node, None)
+ n_ent = Node(ns.bsfs.Entity, n_root)
+ l_root = Literal(ns.bsfs.Literal, None)
+ colors = Feature(
+ uri=ns.bse.colors,
+ parent=ROOT_FEATURE,
+ dimension=1234,
+ dtype=ns.bsfs.float,
+ distance=ns.bsfs.euclidean,
+ )
+
+ # child returns Feature
+ self.assertIsInstance(colors.child(ns.bse.foo), Feature)
+ # uri is respected
+ self.assertEqual(ns.bse.foo, colors.child(ns.bse.foo).uri)
+ # dimension is respected
+ self.assertEqual(4321, colors.child(ns.bse.foo, dimension=4321).dimension)
+ # dtype is respected
+ self.assertEqual(ns.bsfs.integer, colors.child(ns.bse.foo, dtype=ns.bsfs.integer).dtype)
+ # distance is respected
+ self.assertEqual(ns.bsfs.cosine, colors.child(ns.bse.foo, distance=ns.bsfs.cosine).distance)
+ # annotations are respected
+ self.assertDictEqual(colors.child(ns.bse.foo, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123,
+ })
+
+ # dimension is inherited from parent
+ self.assertEqual(1234, colors.child(ns.bse.foo).dimension)
+ # dtype is inherited from parent
+ self.assertEqual(ns.bsfs.float, colors.child(ns.bse.foo).dtype)
+ # distance is inherited from parent
+ self.assertEqual(ns.bsfs.euclidean, colors.child(ns.bse.foo).distance)
## main ##
@@ -222,4 +346,3 @@ if __name__ == '__main__':
unittest.main()
## EOF ##
-
diff --git a/test/triple_store/sparql/test_distance.py b/test/triple_store/sparql/test_distance.py
new file mode 100644
index 0000000..0659459
--- /dev/null
+++ b/test/triple_store/sparql/test_distance.py
@@ -0,0 +1,61 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import numpy as np
+import unittest
+
+# objects to test
+from bsfs.triple_store.sparql import distance
+
+
+## code ##
+
+class TestDistance(unittest.TestCase):
+
+ def test_euclid(self):
+ # self-distance is zero
+ self.assertEqual(distance.euclid([1,2,3,4], [1,2,3,4]), 0.0)
+ # accepts list-like arguments
+ self.assertAlmostEqual(distance.euclid([1,2,3,4], [2,3,4,5]), 2.0, 3)
+ self.assertAlmostEqual(distance.euclid((1,2,3,4), (2,3,4,5)), 2.0, 3)
+ # dimension can vary
+ self.assertAlmostEqual(distance.euclid([1,2,3], [2,3,4]), 1.732, 3)
+ self.assertAlmostEqual(distance.euclid([1,2,3,4,5], [2,3,4,5,6]), 2.236, 3)
+ # vector can be zero
+ self.assertAlmostEqual(distance.euclid([0,0,0], [1,2,3]), 3.742, 3)
+
+ def test_cosine(self):
+ # self-distance is zero
+ self.assertEqual(distance.cosine([1,2,3,4], [1,2,3,4]), 0.0)
+ # accepts list-like arguments
+ self.assertAlmostEqual(distance.cosine([1,2,3,4], [4,3,2,1]), 0.333, 3)
+ self.assertAlmostEqual(distance.cosine((1,2,3,4), (4,3,2,1)), 0.333, 3)
+ # dimension can vary
+ self.assertAlmostEqual(distance.cosine([1,2,3], [3,2,1]), 0.286, 3)
+ self.assertAlmostEqual(distance.cosine([1,2,3,4,5], [5,4,3,2,1]), 0.364, 3)
+ # vector can be zero
+ self.assertAlmostEqual(distance.cosine([0,0,0], [1,2,3]), 1.0, 3)
+
+ def test_manhatten(self):
+ # self-distance is zero
+ self.assertEqual(distance.manhatten([1,2,3,4], [1,2,3,4]), 0.0)
+ # accepts list-like arguments
+ self.assertAlmostEqual(distance.manhatten([1,2,3,4], [2,3,4,5]), 4.0, 3)
+ self.assertAlmostEqual(distance.manhatten((1,2,3,4), (2,3,4,5)), 4.0, 3)
+ # dimension can vary
+ self.assertAlmostEqual(distance.manhatten([1,2,3], [2,3,4]), 3.0, 3)
+ self.assertAlmostEqual(distance.manhatten([1,2,3,4,5], [2,3,4,5,6]), 5.0, 3)
+ # vector can be zero
+ self.assertAlmostEqual(distance.manhatten([0,0,0], [1,2,3]), 6.0, 3)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/triple_store/sparql/test_parse_filter.py b/test/triple_store/sparql/test_parse_filter.py
index bd19803..8764535 100644
--- a/test/triple_store/sparql/test_parse_filter.py
+++ b/test/triple_store/sparql/test_parse_filter.py
@@ -9,7 +9,7 @@ import rdflib
import unittest
# bsie imports
-from bsfs import schema as _schema
+from bsfs import schema as bsc
from bsfs.namespace import ns
from bsfs.query import ast
from bsfs.utils import errors
@@ -23,21 +23,34 @@ from bsfs.triple_store.sparql.parse_filter import Filter
class TestParseFilter(unittest.TestCase):
def setUp(self):
# schema
- self.schema = _schema.Schema.from_string('''
+ self.schema = bsc.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:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Image rdfs:subClassOf bsfs:Entity .
bsfs:Tag rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
bsfs:URI rdfs:subClassOf bsfs:Literal .
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "4"^^xsd:integer ;
+ bsfs:dtype xsd:integer ;
+ bsfs:distance bsfs:euclidean .
+
+ bse:colors rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Colors .
+
bse:comment rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
rdfs:range xsd:string ;
@@ -70,9 +83,6 @@ class TestParseFilter(unittest.TestCase):
''')
- # parser instance
- self.parser = Filter(self.schema)
-
# graph to test queries
self.graph = rdflib.Graph()
# schema hierarchies
@@ -113,6 +123,13 @@ class TestParseFilter(unittest.TestCase):
# image iso
self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.URIRef(ns.bse.iso), rdflib.Literal(1234, datatype=rdflib.XSD.integer)))
self.graph.add((rdflib.URIRef('http://example.com/image#4321'), rdflib.URIRef(ns.bse.iso), rdflib.Literal(4321, datatype=rdflib.XSD.integer)))
+ # color features
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.colors), rdflib.Literal([1,2,3,4], datatype=rdflib.URIRef(ns.bsfs.Colors))))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.colors), rdflib.Literal([4,3,2,1], datatype=rdflib.URIRef(ns.bsfs.Colors))))
+ self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.URIRef(ns.bse.colors), rdflib.Literal([3,4,2,1], datatype=rdflib.URIRef(ns.bsfs.Colors))))
+
+ # parser instance
+ self.parser = Filter(self.graph, self.schema)
def test_routing(self):
@@ -124,7 +141,7 @@ class TestParseFilter(unittest.TestCase):
# __call__ requires a valid root type
self.assertRaises(errors.BackendError, self.parser, self.schema.literal(ns.bsfs.Literal), None)
- self.assertRaises(errors.ConsistencyError, self.parser, self.schema.node(ns.bsfs.Node).get_child(ns.bsfs.Invalid), None)
+ self.assertRaises(errors.ConsistencyError, self.parser, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), None)
# __call__ requires a parseable root
self.assertRaises(errors.BackendError, self.parser, self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression())
# __call__ returns an executable query
@@ -613,6 +630,37 @@ class TestParseFilter(unittest.TestCase):
{'http://example.com/tag#1234'})
+ def test_distance(self):
+ # node colors distance to [2,4,3,1]
+ # entity#1234 [1,2,3,4] 3.742
+ # entity#4321 [4,3,2,1] 2.449
+ # image#1234 [3,4,2,1] 1.414
+
+ # _distance expects a feature
+ self.assertRaises(errors.BackendError, self.parser._distance, self.schema.node(ns.bsfs.Entity), ast.filter.Distance([1,2,3,4], 1), '')
+ # reference must have the correct dimension
+ self.assertRaises(errors.ConsistencyError, self.parser._distance, self.schema.literal(ns.bsfs.Colors), ast.filter.Distance([1,2,3], 1), '')
+ self.assertRaises(errors.ConsistencyError, self.parser._distance, self.schema.literal(ns.bsfs.Colors), ast.filter.Distance([1,2,3,4,5], 1), '')
+ # _distance respects threshold
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([2,4,3,1], 4)))
+ self.assertSetEqual({str(guid) for guid, in self.graph.query(q)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321', 'http://example.com/image#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([2,4,3,1], 3)))
+ self.assertSetEqual({str(guid) for guid, in self.graph.query(q)},
+ {'http://example.com/entity#4321', 'http://example.com/image#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([2,4,3,1], 2)))
+ self.assertSetEqual({str(guid) for guid, in self.graph.query(q)},
+ {'http://example.com/image#1234'})
+ # result set can be empty
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([2,4,3,1], 1)))
+ self.assertSetEqual({str(guid) for guid, in self.graph.query(q)}, set())
+ # _distance respects strict
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([1,2,3,4], 0, False)))
+ self.assertSetEqual({str(guid) for guid, in self.graph.query(q)},
+ {'http://example.com/entity#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([1,2,3,4], 0, True)))
+ self.assertSetEqual({str(guid) for guid, in self.graph.query(q)}, set())
+
def test_one_of(self):
# _one_of expects a node
self.assertRaises(errors.BackendError, self.parser._one_of,
diff --git a/test/triple_store/sparql/test_sparql.py b/test/triple_store/sparql/test_sparql.py
index 3d81de1..7fbfb65 100644
--- a/test/triple_store/sparql/test_sparql.py
+++ b/test/triple_store/sparql/test_sparql.py
@@ -9,7 +9,7 @@ import rdflib
import unittest
# bsie imports
-from bsfs import schema as _schema
+from bsfs import schema as bsc
from bsfs.namespace import ns
from bsfs.query import ast
from bsfs.utils import errors, URI
@@ -22,7 +22,7 @@ from bsfs.triple_store.sparql.sparql import SparqlStore
class TestSparqlStore(unittest.TestCase):
def setUp(self):
- self.schema = _schema.Schema.from_string('''
+ self.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
@@ -33,7 +33,8 @@ class TestSparqlStore(unittest.TestCase):
bsfs:Tag rdfs:subClassOf bsfs:Node .
bsfs:User rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
# non-unique literal
bse:comment rdfs:subClassOf bsfs:Predicate ;
@@ -66,7 +67,11 @@ class TestSparqlStore(unittest.TestCase):
(rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.xsd.string), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Array)),
+ (rdflib.URIRef(ns.bsfs.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Number)),
(rdflib.URIRef(ns.bse.comment), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
@@ -90,7 +95,7 @@ class TestSparqlStore(unittest.TestCase):
def test__has_type(self):
# setup store
store = SparqlStore.Open()
- store.schema = _schema.Schema.from_string('''
+ store.schema = bsc.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/>
@@ -108,7 +113,7 @@ class TestSparqlStore(unittest.TestCase):
store.create(store.schema.node(ns.bsfs.PDF), {URI('http://example.com/me/pdf#1234')})
# node_type must be in the schema
- self.assertRaises(errors.ConsistencyError, store._has_type, URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Node).get_child(ns.bsfs.invalid))
+ self.assertRaises(errors.ConsistencyError, store._has_type, URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Node).child(ns.bsfs.invalid))
# returns False on inexistent nodes
self.assertFalse(store._has_type(URI('http://example.com/me/entity#4321'), store.schema.node(ns.bsfs.Entity)))
@@ -195,7 +200,7 @@ class TestSparqlStore(unittest.TestCase):
self.assertSetEqual(set(store._graph), instances)
# add some classes to the schema
- curr = curr + _schema.Schema.from_string('''
+ curr = curr + bsc.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/>
@@ -298,7 +303,7 @@ class TestSparqlStore(unittest.TestCase):
# remove some classes from the schema
- curr = _schema.Schema.from_string('''
+ curr = bsc.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/>
@@ -310,7 +315,8 @@ class TestSparqlStore(unittest.TestCase):
bsfs:User rdfs:subClassOf bsfs:Node .
xsd:boolean rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Number .
bse:filesize rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -351,7 +357,11 @@ class TestSparqlStore(unittest.TestCase):
(rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Array)),
+ (rdflib.URIRef(ns.bsfs.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsfs.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Number)),
(rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
@@ -382,8 +392,25 @@ class TestSparqlStore(unittest.TestCase):
class Foo(): pass
self.assertRaises(TypeError, setattr, store, 'schema', Foo())
+ # cannot define features w/o known distance function
+ invalid = bsc.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:Array rdfs:subClassOf bsfs:Literal .
+ bsfs:Feature rdfs:subClassOf bsfs:Array .
+
+ bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:dimension "4"^^xsd:integer ;
+ bsfs:distance bsfs:foobar .
+
+ ''')
+ self.assertRaises(errors.UnsupportedError, setattr, store, 'schema', invalid)
+
# cannot migrate to incompatible schema
- invalid = _schema.Schema.from_string('''
+ invalid = bsc.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/>
@@ -399,7 +426,7 @@ class TestSparqlStore(unittest.TestCase):
''')
self.assertRaises(errors.ConsistencyError, setattr, store, 'schema', invalid)
- invalid = _schema.Schema.from_string('''
+ invalid = bsc.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/>
@@ -509,7 +536,7 @@ class TestSparqlStore(unittest.TestCase):
store.set(ent_type, {URI('http://example.com/me/entity#1234')}, self.schema.predicate(ns.bse.filesize), {1234})
store.set(ent_type, {URI('http://example.com/me/entity#4321')}, self.schema.predicate(ns.bse.filesize), {4321})
# node_type must be in the schema
- self.assertRaises(errors.ConsistencyError, set, store.get(self.schema.node(ns.bsfs.Node).get_child(ns.bsfs.Invalid), ast.filter.IsIn(ent_ids)))
+ self.assertRaises(errors.ConsistencyError, set, store.get(self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), ast.filter.IsIn(ent_ids)))
# query must be a filter expression
class Foo(): pass
self.assertRaises(TypeError, set, store.get(ent_type, 1234))
@@ -574,7 +601,7 @@ class TestSparqlStore(unittest.TestCase):
store.schema = self.schema
# node type must be valid
- self.assertRaises(errors.ConsistencyError, store.create, self.schema.node(ns.bsfs.Entity).get_child(ns.bsfs.invalid), {
+ self.assertRaises(errors.ConsistencyError, store.create, self.schema.node(ns.bsfs.Entity).child(ns.bsfs.invalid), {
URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
# can create some nodes
@@ -636,7 +663,7 @@ class TestSparqlStore(unittest.TestCase):
p_comment = store.schema.predicate(ns.bse.comment)
p_author = store.schema.predicate(ns.bse.author)
p_tag = store.schema.predicate(ns.bse.tag)
- p_invalid = store.schema.predicate(ns.bsfs.Predicate).get_child(ns.bsfs.foo, range=store.schema.node(ns.bsfs.Tag))
+ p_invalid = store.schema.predicate(ns.bsfs.Predicate).child(ns.bsfs.foo, range=store.schema.node(ns.bsfs.Tag))
# create node instances
ent_ids = {
URI('http://example.com/me/entity#1234'),
@@ -659,7 +686,7 @@ class TestSparqlStore(unittest.TestCase):
store.create(user_type, user_ids)
# invalid node_type is not permitted
- self.assertRaises(errors.ConsistencyError, store.set, self.schema.node(ns.bsfs.Node).get_child(ns.bse.foo),
+ self.assertRaises(errors.ConsistencyError, store.set, self.schema.node(ns.bsfs.Node).child(ns.bse.foo),
ent_ids, p_comment, {'hello world'})
# invalid predicate is not permitted
diff --git a/test/utils/test_uuid.py b/test/utils/test_uuid.py
index 49176d4..0de96ed 100644
--- a/test/utils/test_uuid.py
+++ b/test/utils/test_uuid.py
@@ -83,6 +83,10 @@ class TestUCID(unittest.TestCase):
def test_from_path(self):
self.assertEqual(UCID.from_path(self._path), self._checksum)
+ def test_from_dict(self):
+ self.assertEqual(UCID.from_dict({'hello': 'world', 'foo': 1234, 'bar': False}),
+ '8d2544395a0d2827e3d9ce8cd619d5e3f801e8126bf3f93ee5abd38158959585')
+
## main ##