aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/__init__.py0
-rw-r--r--test/apps/__init__.py0
-rw-r--r--test/apps/config.json8
-rw-r--r--test/apps/schema-1.nt19
-rw-r--r--test/apps/schema-2.nt19
-rw-r--r--test/apps/test_init.py91
-rw-r--r--test/apps/test_migrate.py66
-rw-r--r--test/front/__init__.py0
-rw-r--r--test/front/test_bsfs.py38
-rw-r--r--test/front/test_builder.py64
-rw-r--r--test/graph/__init__.py0
-rw-r--r--test/graph/ac/__init__.py0
-rw-r--r--test/graph/ac/test_null.py102
-rw-r--r--test/graph/test_graph.py201
-rw-r--r--test/graph/test_nodes.py361
-rw-r--r--test/namespace/__init__.py0
-rw-r--r--test/namespace/test_namespace.py132
-rw-r--r--test/schema/__init__.py0
-rw-r--r--test/schema/test_schema.py745
-rw-r--r--test/schema/test_types.py225
-rw-r--r--test/triple_store/__init__.py0
-rw-r--r--test/triple_store/test_base.py150
-rw-r--r--test/triple_store/test_sparql.py769
-rw-r--r--test/utils/__init__.py0
-rw-r--r--test/utils/test_commons.py31
-rw-r--r--test/utils/test_uri.py189
-rw-r--r--test/utils/test_uuid.py92
-rw-r--r--test/utils/testfile.t1
28 files changed, 3303 insertions, 0 deletions
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/__init__.py
diff --git a/test/apps/__init__.py b/test/apps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/apps/__init__.py
diff --git a/test/apps/config.json b/test/apps/config.json
new file mode 100644
index 0000000..ffc5ef7
--- /dev/null
+++ b/test/apps/config.json
@@ -0,0 +1,8 @@
+{
+ "Graph": {
+ "user": "http://example.com/me",
+ "backend": {
+ "SparqlStore": {}
+ }
+ }
+}
diff --git a/test/apps/schema-1.nt b/test/apps/schema-1.nt
new file mode 100644
index 0000000..e57146d
--- /dev/null
+++ b/test/apps/schema-1.nt
@@ -0,0 +1,19 @@
+
+prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+# common bsfs prefixes
+prefix bsfs: <http://bsfs.ai/schema/>
+prefix bse: <http://bsfs.ai/schema/Entity#>
+
+# essential nodes
+bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+# common definitions
+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 .
+
diff --git a/test/apps/schema-2.nt b/test/apps/schema-2.nt
new file mode 100644
index 0000000..525ac99
--- /dev/null
+++ b/test/apps/schema-2.nt
@@ -0,0 +1,19 @@
+
+prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+# common bsfs prefixes
+prefix bsfs: <http://bsfs.ai/schema/>
+prefix bse: <http://bsfs.ai/schema/Entity#>
+
+# essential nodes
+bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+# common definitions
+xsd:integer rdfs:subClassOf bsfs:Literal .
+
+bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
diff --git a/test/apps/test_init.py b/test/apps/test_init.py
new file mode 100644
index 0000000..bae6a68
--- /dev/null
+++ b/test/apps/test_init.py
@@ -0,0 +1,91 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import contextlib
+import io
+import json
+import os
+import tempfile
+import unittest
+
+# bsie imports
+from bsfs.front import build_graph
+from bsfs.graph import Graph
+
+# objects to test
+from bsfs.apps.init import main, init_sparql_store
+
+
+## code ##
+
+class TestInit(unittest.TestCase):
+ def test_main(self):
+
+ # cannot pass an invalid store
+ with contextlib.redirect_stderr(io.StringIO()):
+ self.assertRaises(SystemExit, main, ['--user', 'http://example.com/me', 'foobar'])
+
+ # produces a config structure
+ outbuf = io.StringIO()
+ with contextlib.redirect_stdout(outbuf):
+ main(['--user', 'http://example.com/me', 'sparql'])
+ self.assertEqual(json.loads(outbuf.getvalue()), {
+ 'Graph': {
+ 'user': 'http://example.com/me',
+ 'backend': {
+ 'SparqlStore': {}}}})
+ # config is valid
+ self.assertIsInstance(build_graph(json.loads(outbuf.getvalue())), Graph)
+
+ # respects user flag
+ outbuf = io.StringIO()
+ with contextlib.redirect_stdout(outbuf):
+ main(['--user', 'http://example.com/you', 'sparql'])
+ self.assertEqual(json.loads(outbuf.getvalue()), {
+ 'Graph': {
+ 'user': 'http://example.com/you',
+ 'backend': {
+ 'SparqlStore': {}}}})
+
+ # respects output flag
+ _, path = tempfile.mkstemp(prefix='bsfs-test-', text=True)
+ outbuf = io.StringIO()
+ with contextlib.redirect_stdout(outbuf):
+ main(['--user', 'http://example.com/me', '--output', path, 'sparql'])
+ with open(path, 'rt') as ifile:
+ config = ifile.read()
+ os.unlink(path)
+ self.assertEqual(outbuf.getvalue(), '')
+ self.assertEqual(json.loads(config), {
+ 'Graph': {
+ 'user': 'http://example.com/me',
+ 'backend': {
+ 'SparqlStore': {}}}})
+
+ def test_init_sparql_store(self):
+ # returns a config structure
+ self.assertEqual(init_sparql_store('http://example.com/me'), {
+ 'Graph': {
+ 'user': 'http://example.com/me',
+ 'backend': {
+ 'SparqlStore': {}}}})
+ # respects user
+ self.assertEqual(init_sparql_store('http://example.com/you'), {
+ 'Graph': {
+ 'user': 'http://example.com/you',
+ 'backend': {
+ 'SparqlStore': {}}}})
+ # the config is valid
+ self.assertIsInstance(build_graph(init_sparql_store('http://example.com/me')), Graph)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/apps/test_migrate.py b/test/apps/test_migrate.py
new file mode 100644
index 0000000..957509a
--- /dev/null
+++ b/test/apps/test_migrate.py
@@ -0,0 +1,66 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import contextlib
+import io
+import os
+import sys
+import unittest
+import unittest.mock
+
+# bsie imports
+from bsfs.schema import Schema
+
+# objects to test
+from bsfs.apps.migrate import main
+
+
+## code ##
+
+class TestMigrate(unittest.TestCase):
+ def test_main(self):
+ config = os.path.join(os.path.dirname(__file__), 'config.json')
+ schema_1 = os.path.join(os.path.dirname(__file__), 'schema-1.nt')
+ schema_2 = os.path.join(os.path.dirname(__file__), 'schema-2.nt')
+
+ # provide no config
+ with contextlib.redirect_stderr(io.StringIO()):
+ self.assertRaises(SystemExit, main, [])
+
+ # read schema from file
+ with open(schema_1) as ifile:
+ 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())
+ with open(schema_2) as ifile:
+ 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())
+ with open(schema_1, 'rt') as ifile:
+ with unittest.mock.patch('sys.stdin', ifile):
+ graph = main([config])
+ self.assertTrue(target <= graph.schema)
+
+ # remove predicates
+ # NOTE: cannot currently test this since there's nothing to remove in the loaded (empty) schema.
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
+
diff --git a/test/front/__init__.py b/test/front/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/front/__init__.py
diff --git a/test/front/test_bsfs.py b/test/front/test_bsfs.py
new file mode 100644
index 0000000..0d7f383
--- /dev/null
+++ b/test/front/test_bsfs.py
@@ -0,0 +1,38 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import unittest
+
+# bsie imports
+from bsfs.graph import Graph
+from bsfs.triple_store import SparqlStore
+from bsfs.utils import errors, URI
+
+# objects to test
+from bsfs.front.bsfs import Open
+
+
+## code ##
+
+class TestBSFS(unittest.TestCase):
+ def test_open(self):
+ # valid config produces a valid graph
+ config = {'Graph': {'backend': {'SparqlStore': {}}, 'user': 'http://example.com/me'}}
+ graph = Open(config)
+ self.assertIsInstance(graph, Graph)
+ self.assertIsInstance(graph._backend, SparqlStore)
+ self.assertEqual(graph._user, URI('http://example.com/me'))
+ # invalid config raises an error
+ self.assertRaises(errors.ConfigError, Open, {})
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/front/test_builder.py b/test/front/test_builder.py
new file mode 100644
index 0000000..08f2027
--- /dev/null
+++ b/test/front/test_builder.py
@@ -0,0 +1,64 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import unittest
+
+# bsie imports
+from bsfs.graph import Graph
+from bsfs.triple_store import SparqlStore
+from bsfs.utils import errors, URI
+
+# objects to test
+from bsfs.front.builder import build_backend, build_graph
+
+
+## code ##
+
+class TestBuilder(unittest.TestCase):
+ def test_build_backend(self):
+ # valid config produces a valid store
+ store = build_backend({'SparqlStore': {}})
+ self.assertIsInstance(store, SparqlStore)
+ self.assertIsNone(store.uri)
+ # cannot create an invalid store
+ self.assertRaises(errors.ConfigError, build_backend, {'MyStore': {}})
+ # must pass a dict
+ self.assertRaises(TypeError, build_backend, 1234)
+ self.assertRaises(TypeError, build_backend, 'hello world')
+ self.assertRaises(TypeError, build_backend, [1,2,3])
+ # cannot create a store from an invalid config
+ self.assertRaises(errors.ConfigError, build_backend, {})
+ self.assertRaises(errors.ConfigError, build_backend, {'SparqlStore': {}, 'OtherStore': {}})
+ self.assertRaises(TypeError, build_backend, {'SparqlStore': {'hello': 'world'}})
+
+ def test_build_graph(self):
+ # valid config produces a valid graph
+ graph = build_graph({'Graph': {'backend': {'SparqlStore': {}}, 'user': 'http://example.com/me'}})
+ self.assertIsInstance(graph, Graph)
+ self.assertIsInstance(graph._backend, SparqlStore)
+ self.assertEqual(graph._user, URI('http://example.com/me'))
+ # cannot create an invalid graph
+ self.assertRaises(errors.ConfigError, build_graph, {'MyGraph': {}})
+ # must pass a dict
+ self.assertRaises(TypeError, build_graph, 1234)
+ self.assertRaises(TypeError, build_graph, 'hello world')
+ self.assertRaises(TypeError, build_graph, [1,2,3])
+ # cannot create a graph from an invalid config
+ self.assertRaises(errors.ConfigError, build_graph, {})
+ self.assertRaises(errors.ConfigError, build_graph, {'Graph': {}, 'Graph2': {}})
+ self.assertRaises(errors.ConfigError, build_graph, {'Graph': {}})
+ self.assertRaises(errors.ConfigError, build_graph, {'Graph': {'user': 'http://example.com/me'}})
+ self.assertRaises(errors.ConfigError, build_graph, {'Graph': {'backend': 'Hello world'}})
+ self.assertRaises(TypeError, build_graph, {'Graph': {'user': 'http://example.com/me', 'backend': 'Hello world'}})
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/graph/__init__.py b/test/graph/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/graph/__init__.py
diff --git a/test/graph/ac/__init__.py b/test/graph/ac/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/graph/ac/__init__.py
diff --git a/test/graph/ac/test_null.py b/test/graph/ac/test_null.py
new file mode 100644
index 0000000..f39c9be
--- /dev/null
+++ b/test/graph/ac/test_null.py
@@ -0,0 +1,102 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import unittest
+
+# bsie imports
+from bsfs import schema as _schema
+from bsfs.namespace import ns
+from bsfs.triple_store import SparqlStore
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.graph.ac.null import NullAC
+
+
+## code ##
+
+class TestNullAC(unittest.TestCase):
+ def setUp(self):
+ self.backend = SparqlStore()
+ self.backend.schema = _schema.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#>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+
+ # predicates mandated by Nodes
+ bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # additionally defined predicates
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ self.user = URI('http://www.example.com/me')
+ self.p_author = self.backend.schema.predicate(ns.bse.author)
+ self.p_filesize = self.backend.schema.predicate(ns.bse.filesize)
+ self.p_tag = self.backend.schema.predicate(ns.bse.tag)
+ self.p_created = self.backend.schema.predicate(ns.bsm.t_created)
+ self.ent_type = self.backend.schema.node(ns.bsfs.Entity)
+ self.ent_ids = {URI('http://www.example.com/me/entity#1234'), URI('http://www.example.com/me/entity#4321')}
+
+ def test_is_protected_predicate(self):
+ ac = NullAC(self.backend, self.user)
+ self.assertTrue(ac.is_protected_predicate(self.p_created))
+ self.assertFalse(ac.is_protected_predicate(self.p_filesize))
+ self.assertFalse(ac.is_protected_predicate(self.p_author))
+ self.assertFalse(ac.is_protected_predicate(self.p_tag))
+
+ def test_create(self):
+ ac = NullAC(self.backend, self.user)
+ self.assertEqual(None, ac.create(self.ent_type, self.ent_ids))
+
+ def test_link_from_node(self):
+ ac = NullAC(self.backend, self.user)
+ self.assertSetEqual(self.ent_ids, ac.link_from_node(self.ent_type, self.ent_ids))
+
+ def test_link_to_node(self):
+ ac = NullAC(self.backend, self.user)
+ self.assertSetEqual(self.ent_ids, ac.link_to_node(self.ent_type, self.ent_ids))
+
+ def test_write_literal(self):
+ ac = NullAC(self.backend, self.user)
+ self.assertSetEqual(self.ent_ids, ac.write_literal(self.ent_type, self.ent_ids))
+
+ def test_createable(self):
+ ac = NullAC(self.backend, self.user)
+ self.assertSetEqual(self.ent_ids, ac.createable(self.ent_type, self.ent_ids))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/graph/test_graph.py b/test/graph/test_graph.py
new file mode 100644
index 0000000..33cf6aa
--- /dev/null
+++ b/test/graph/test_graph.py
@@ -0,0 +1,201 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import unittest
+
+# bsie imports
+from bsfs import schema
+from bsfs.namespace import ns
+from bsfs.triple_store import SparqlStore
+from bsfs.utils import URI, errors
+from bsfs.graph.nodes import Nodes
+
+# objects to test
+from bsfs.graph.graph import Graph
+
+
+## code ##
+
+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('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ ''')
+
+ def test_str(self):
+ self.assertEqual(str(Graph(self.backend, self.user)),
+ 'Graph(SparqlStore(uri=None), http://example.com/me)')
+ self.assertEqual(repr(Graph(self.backend, self.user)),
+ 'Graph(backend=SparqlStore(uri=None), user=http://example.com/me)')
+ # str respects backend
+ class Foo(SparqlStore): pass
+ self.assertEqual(str(Graph(Foo.Open(), self.user)),
+ 'Graph(Foo(uri=None), http://example.com/me)')
+ self.assertEqual(repr(Graph(Foo.Open(), self.user)),
+ 'Graph(backend=Foo(uri=None), user=http://example.com/me)')
+ # str respect user
+ self.assertEqual(str(Graph(self.backend, URI('http://example.com/you'))),
+ 'Graph(SparqlStore(uri=None), http://example.com/you)')
+ self.assertEqual(repr(Graph(self.backend, URI('http://example.com/you'))),
+ 'Graph(backend=SparqlStore(uri=None), user=http://example.com/you)')
+ # str respects type
+ class Bar(Graph): pass
+ self.assertEqual(str(Bar(self.backend, self.user)),
+ 'Bar(SparqlStore(uri=None), http://example.com/me)')
+ self.assertEqual(repr(Bar(self.backend, self.user)),
+ 'Bar(backend=SparqlStore(uri=None), user=http://example.com/me)')
+
+ def test_equality(self):
+ graph = Graph(self.backend, self.user)
+ # instance is equal to itself
+ self.assertEqual(graph, graph)
+ self.assertEqual(hash(graph), hash(graph))
+ # instance is equal to a clone
+ self.assertEqual(graph, Graph(self.backend, self.user))
+ self.assertEqual(hash(graph), hash(Graph(self.backend, self.user)))
+ # equality respects backend
+ self.assertNotEqual(graph, Graph(SparqlStore.Open(), self.user))
+ self.assertNotEqual(hash(graph), hash(Graph(SparqlStore.Open(), self.user)))
+ # equality respects user
+ self.assertNotEqual(graph, Graph(self.backend, URI('http://example.com/you')))
+ self.assertNotEqual(hash(graph), hash(Graph(self.backend, URI('http://example.com/you'))))
+
+ def test_essentials(self):
+ graph = Graph(self.backend, self.user)
+ # schema
+ self.assertEqual(graph.schema, self.backend.schema)
+ self.assertRaises(AttributeError, setattr, graph, 'schema', None)
+
+ def test_node(self):
+ graph = Graph(self.backend, self.user)
+ guid = URI('http://example.com/me/entity#1234')
+ # returns a Nodes instance
+ self.assertEqual(
+ graph.node(ns.bsfs.Entity, guid),
+ Nodes(self.backend, self.user, graph.schema.node(ns.bsfs.Entity), {guid}))
+ # node_type must be in the schema
+ self.assertRaises(KeyError, graph.node, ns.bsfs.Invalid, guid)
+
+ def test_nodes(self):
+ graph = Graph(self.backend, self.user)
+ guids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ # returns a Nodes instance
+ self.assertEqual(
+ graph.nodes(ns.bsfs.Entity, guids),
+ Nodes(self.backend, self.user, graph.schema.node(ns.bsfs.Entity), guids))
+ # node_type must be in the schema
+ self.assertRaises(KeyError, graph.nodes, ns.bsfs.Invalid, guids)
+
+ def test_migrate(self):
+ # setup
+ graph = Graph(self.backend, self.user)
+
+ # argument must be a schema
+ class Foo(): pass
+ self.assertRaises(TypeError, graph.migrate, 'hello world')
+ self.assertRaises(TypeError, graph.migrate, 1234)
+ self.assertRaises(TypeError, graph.migrate, Foo())
+
+ # cannot append inconsistent schema
+ self.assertRaises(errors.ConsistencyError, graph.migrate, schema.Schema({}, {
+ schema.Node(ns.bsfs.Entity,
+ schema.Node(ns.bsfs.Intermediate,
+ schema.Node(ns.bsfs.Node, None)))}), append=True)
+
+ # cannot migrate to inconsistent schema
+ self.assertRaises(errors.ConsistencyError, graph.migrate, schema.Schema({}, {
+ schema.Node(ns.bsfs.Entity,
+ schema.Node(ns.bsfs.Intermediate,
+ schema.Node(ns.bsfs.Node, None)))}), append=False)
+
+ # can migrate to compatible schema
+ target_1 = schema.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 .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ graph.migrate(target_1)
+ # new schema is applied
+ self.assertLess(target_1, graph.schema)
+ # graph appends its predicates
+ self.assertEqual(graph.schema, target_1 + schema.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 .
+ bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+ '''))
+
+ # can overwrite the current schema
+ target_2 = schema.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 .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ ''')
+ graph.migrate(target_2, append=False)
+ # append overwrites existing predicates
+ self.assertFalse(target_1 <= graph.schema)
+ # new schema is applied
+ self.assertLess(target_2, graph.schema)
+ # graph appends its predicates
+ self.assertEqual(graph.schema, target_2 + schema.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 .
+ bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+ '''))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/graph/test_nodes.py b/test/graph/test_nodes.py
new file mode 100644
index 0000000..43e7f6f
--- /dev/null
+++ b/test/graph/test_nodes.py
@@ -0,0 +1,361 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import rdflib
+import unittest
+
+# bsie imports
+from bsfs import schema as _schema
+from bsfs.namespace import ns
+from bsfs.triple_store.sparql import SparqlStore
+from bsfs.utils import errors, URI
+
+# objects to test
+from bsfs.graph.nodes import Nodes
+
+
+## code ##
+
+class TestNodes(unittest.TestCase):
+ def setUp(self):
+ # initialize backend
+ self.backend = SparqlStore()
+ self.backend.schema = _schema.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#>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bst: <http://bsfs.ai/schema/Tag#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:User rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+
+ # predicates mandated by Nodes
+ bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # additionally defined predicates
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:User ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bst:representative rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Entity ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ ''')
+ # Nodes constructor args
+ self.user = URI('http://example.com/me')
+ # set args
+ self.tag_type = self.backend.schema.node(ns.bsfs.Tag)
+ self.ent_type = self.backend.schema.node(ns.bsfs.Entity)
+ self.user_type = self.backend.schema.node(ns.bsfs.User)
+ self.p_filesize = self.backend.schema.predicate(ns.bse.filesize)
+ self.p_author = self.backend.schema.predicate(ns.bse.author)
+ self.p_tag = self.backend.schema.predicate(ns.bse.tag)
+ self.p_representative = self.backend.schema.predicate(URI('http://bsfs.ai/schema/Tag#representative'))
+ self.t_created = self.backend.schema.predicate(ns.bsm.t_created)
+ self.ent_ids = {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321'),
+ }
+ self.tag_ids = {
+ URI('http://example.com/me/tag#1234'),
+ URI('http://example.com/me/tag#4321'),
+ }
+
+ def test_str(self):
+ # str baseline
+ nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ self.assertEqual(str(nodes), f'Nodes({self.ent_type}, {self.ent_ids})')
+ self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.user}, {self.ent_type}, {self.ent_ids})')
+ # str respects node_type
+ nodes = Nodes(self.backend, self.user, self.tag_type, self.tag_ids)
+ self.assertEqual(str(nodes), f'Nodes({self.tag_type}, {self.tag_ids})')
+ self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.user}, {self.tag_type}, {self.tag_ids})')
+ # str respects guids
+ nodes = Nodes(self.backend, self.user, self.ent_type, {URI('http://example.com/me/entity#foo')})
+ self.assertEqual(str(nodes), f'Nodes({self.ent_type}, {{\'http://example.com/me/entity#foo\'}})')
+ self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.user}, {self.ent_type}, {{\'http://example.com/me/entity#foo\'}})')
+ # repr respects backend
+ class Foo(SparqlStore): pass
+ backend = Foo.Open()
+ backend.schema = self.backend.schema
+ nodes = Nodes(backend, self.user, self.ent_type, self.ent_ids)
+ self.assertEqual(repr(nodes), f'Nodes({backend}, {self.user}, {self.ent_type}, {self.ent_ids})')
+ # repr respects user
+ nodes = Nodes(self.backend, URI('http://example.com/you'), self.ent_type, self.ent_ids)
+ self.assertEqual(repr(nodes), f'Nodes({self.backend}, http://example.com/you, {self.ent_type}, {self.ent_ids})')
+
+ def test_equality(self):
+ nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ # instance is equal to itself
+ self.assertEqual(nodes, nodes)
+ self.assertEqual(hash(nodes), hash(nodes))
+ # instance is equal to a clone
+ self.assertEqual(nodes, Nodes(self.backend, self.user, self.ent_type, self.ent_ids))
+ self.assertEqual(Nodes(self.backend, self.user, self.ent_type, self.ent_ids), nodes)
+ self.assertEqual(hash(nodes), hash(Nodes(self.backend, self.user, self.ent_type, self.ent_ids)))
+ # equality respects backend
+ backend = SparqlStore.Open()
+ backend.schema = self.backend.schema
+ self.assertNotEqual(nodes, Nodes(backend, self.user, self.ent_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(backend, self.user, self.ent_type, self.ent_ids)))
+ # equality respects user
+ self.assertNotEqual(nodes, Nodes(self.backend, URI('http://example.com/you'), self.ent_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, URI('http://example.com/you'), self.ent_type, self.ent_ids)))
+ # equality respects node_type
+ self.assertNotEqual(nodes, Nodes(self.backend, self.user, self.tag_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.user, self.tag_type, self.ent_ids)))
+ # equality respects guids
+ self.assertNotEqual(nodes, Nodes(self.backend, self.user, self.ent_type, self.tag_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.user, self.ent_type, self.tag_ids)))
+
+ def test_properties(self):
+ # node_type
+ self.assertEqual(self.ent_type, Nodes(
+ self.backend, self.user, self.ent_type, self.ent_ids).node_type)
+ self.assertEqual(self.tag_type, Nodes(
+ self.backend, self.user, self.tag_type, self.tag_ids).node_type)
+ # guids
+ self.assertSetEqual(self.ent_ids, set(Nodes(
+ self.backend, self.user, self.ent_type, self.ent_ids).guids))
+ self.assertSetEqual(self.tag_ids, set(Nodes(
+ self.backend, self.user, self.tag_type, self.tag_ids).guids))
+
+ def test__ensure_nodes(self):
+ nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+
+ # missing nodes are created
+ self.assertSetEqual(self.ent_ids, nodes._ensure_nodes(self.ent_type, self.ent_ids))
+ # get creation time from backend manually
+ time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri)))
+ t_ent_created = float(time_triples[0]) if len(time_triples) > 0 else 0.0
+ # check triples
+ self.assertSetEqual(set(self.backend._graph), {
+ # entity definitions
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ # bookkeeping
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ })
+
+ # existing nodes remain unchanged
+ self.assertSetEqual(self.ent_ids, nodes._ensure_nodes(self.ent_type, self.ent_ids))
+ self.assertSetEqual(set(self.backend._graph), {
+ # entity definitions
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ # bookkeeping
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ })
+
+ # type and guids don't need to match the node instance's members
+ self.assertSetEqual(self.tag_ids, nodes._ensure_nodes(self.tag_type, self.tag_ids))
+ # get creation time from backend manually
+ time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri)))
+ t_tag_created = float(time_triples[0]) if len(time_triples) > 0 else 0.0
+ # check triples
+ self.assertSetEqual(set(self.backend._graph), {
+ # previous triples
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ # new triples
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)),
+ })
+
+ def test___set(self):
+ # setup
+ nodes = Nodes(self.backend, self.user, self.ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ self.assertSetEqual(set(self.backend._graph), set())
+ set_ = nodes._Nodes__set
+
+ # node_type must match predicate's domain
+ self.assertRaises(errors.ConsistencyError, set_, self.p_representative.uri, self.ent_ids)
+
+ # cannot set protected predicates
+ self.assertRaises(errors.PermissionDeniedError, set_, self.t_created.uri, 1234)
+
+ # set literal value
+ set_(self.p_filesize.uri, 1234)
+ # get creation time from backend manually
+ time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri)))
+ t_ent_created = float(time_triples[0]) if len(time_triples) > 0 else 0.0
+ # verify triples
+ self.assertSetEqual(set(self.backend._graph), {
+ # entity definitions
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ # bookkeeping
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ # literals
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+ # set node value
+ tags = Nodes(self.backend, self.user, self.tag_type, {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ set_(self.p_tag.uri, tags)
+ # get creation time from backend manually
+ time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri)))
+ t_tag_created = float(time_triples[0]) if len(time_triples) > 0 else 0.0
+ # verify triples
+ self.assertSetEqual(set(self.backend._graph), {
+ # previous values
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
+ # tag definitions
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ # tag bookkeeping
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)),
+ # entity -> tag links
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ })
+ # value must be a nodes instance
+ self.assertRaises(TypeError, set_, self.p_tag.uri, 'foobar')
+ self.assertRaises(TypeError, set_, self.p_tag.uri, self.tag_ids)
+ self.assertRaises(TypeError, set_, self.p_tag.uri, URI('http://example.com/me/tag#1234'))
+ # value's node_type must match the predicate's range
+ self.assertRaises(errors.ConsistencyError, set_, self.p_tag.uri,
+ Nodes(self.backend, self.user, self.ent_type, self.ent_ids))
+
+ def test_set(self):
+ self.assertSetEqual(set(self.backend._graph), set())
+ nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ # can set literal values
+ self.assertEqual(nodes, nodes.set(self.p_filesize.uri, 1234))
+ self.assertTrue(set(self.backend._graph).issuperset({
+ # nodes exist
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ # links exist
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ }))
+ # can set node values
+ self.assertEqual(nodes, nodes.set(self.p_tag.uri, Nodes(self.backend, self.user, self.tag_type, self.tag_ids)))
+ self.assertTrue(set(self.backend._graph).issuperset({
+ # nodes exist
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ # links exist
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ }))
+
+ # cannot set protected predicate
+ curr = set(self.backend._graph)
+ self.assertRaises(errors.PermissionDeniedError, nodes.set, self.t_created.uri, 12345)
+ self.assertSetEqual(curr, set(self.backend._graph))
+ # predicate.domain must match node_type
+ self.assertRaises(errors.ConsistencyError, nodes.set, self.p_representative.uri, nodes)
+ self.assertSetEqual(curr, set(self.backend._graph))
+ # value's node_type must match predicate's range
+ self.assertRaises(errors.ConsistencyError, nodes.set, self.p_tag.uri, nodes)
+ self.assertSetEqual(curr, set(self.backend._graph))
+ # value type must match predicate's range type
+ self.assertRaises(TypeError, nodes.set, self.p_tag.uri, 'invalid')
+ self.assertSetEqual(curr, set(self.backend._graph))
+ # cannot assing multiple values to unique predicate
+ self.assertRaises(ValueError, nodes.set, self.p_author.uri,
+ Nodes(self.backend, self.user, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')}))
+ self.assertSetEqual(curr, set(self.backend._graph))
+
+
+ def test_set_from_iterable(self):
+ self.assertSetEqual(set(self.backend._graph), set())
+ nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ # can set literal and node values simultaneously
+ self.assertEqual(nodes, nodes.set_from_iterable({
+ self.p_filesize.uri: 1234,
+ self.p_tag.uri: Nodes(self.backend, self.user, self.tag_type, self.tag_ids),
+ }.items()))
+ self.assertTrue(set(self.backend._graph).issuperset({
+ # nodes exist
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ # links exist
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ }))
+
+ # cannot set protected predicate
+ curr = set(self.backend._graph)
+ self.assertRaises(errors.PermissionDeniedError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.t_created.uri, 12345)))
+ self.assertSetEqual(curr, set(self.backend._graph))
+ # predicate.domain must match node_type
+ self.assertRaises(errors.ConsistencyError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.p_representative.uri, nodes)))
+ self.assertSetEqual(curr, set(self.backend._graph))
+ # value's node_type must match predicate's range
+ self.assertRaises(errors.ConsistencyError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.p_tag.uri, nodes)))
+ self.assertSetEqual(curr, set(self.backend._graph))
+ # value type must match predicate's range type
+ self.assertRaises(TypeError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234), (self.p_tag.uri, 'invalid')))
+ self.assertSetEqual(curr, set(self.backend._graph))
+ # cannot assing multiple values to unique predicate
+ self.assertRaises(ValueError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234),
+ (self.p_author.uri, Nodes(self.backend, self.user, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')}))))
+ self.assertSetEqual(curr, set(self.backend._graph))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/namespace/__init__.py b/test/namespace/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/namespace/__init__.py
diff --git a/test/namespace/test_namespace.py b/test/namespace/test_namespace.py
new file mode 100644
index 0000000..f109653
--- /dev/null
+++ b/test/namespace/test_namespace.py
@@ -0,0 +1,132 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import operator
+import unittest
+
+# bsfs imports
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.namespace.namespace import Namespace, ClosedNamespace
+
+
+## code ##
+
+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(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(repr(Namespace('http://example.org///##')), 'Namespace(http://example.org, #, /)')
+ # comparison
+ class Foo(Namespace): pass
+ self.assertEqual(Namespace('http://example.org/'), Namespace('http://example.org/'))
+ self.assertEqual(Namespace('http://example.org/'), Namespace('http://example.org'))
+ self.assertEqual(Namespace('http://example.org/'), Namespace('http://example.org#'))
+ self.assertNotEqual(Namespace('http://example.org'), Namespace('http://example.org', fsep='.'))
+ self.assertNotEqual(Namespace('http://example.org'), Namespace('http://example.org', psep='.'))
+ self.assertNotEqual(Namespace('http://example.org/'), Foo('http://example.org/'))
+ self.assertNotEqual(Foo('http://example.org/'), Namespace('http://example.org/'))
+ # hashing
+ self.assertEqual(hash(Namespace('http://example.org/')), hash(Namespace('http://example.org/')))
+ self.assertEqual(hash(Namespace('http://example.org/')), hash(Namespace('http://example.org')))
+ self.assertEqual(hash(Namespace('http://example.org/')), hash(Namespace('http://example.org#')))
+ self.assertNotEqual(hash(Namespace('http://example.org')), hash(Namespace('http://example.com')))
+ self.assertNotEqual(hash(Namespace('http://example.org')), hash(Namespace('http://example.org', fsep='.')))
+ self.assertNotEqual(hash(Namespace('http://example.org')), hash(Namespace('http://example.org', psep='.')))
+ self.assertNotEqual(hash(Namespace('http://example.org/')), hash(Foo('http://example.org/')))
+ self.assertNotEqual(hash(Foo('http://example.org/')), hash(Namespace('http://example.org/')))
+
+ def test_getattr(self):
+ self.assertEqual(Namespace('http://example.org/').foo, 'http://example.org#foo')
+ self.assertEqual(Namespace('http://example.org/').bar, 'http://example.org#bar')
+ self.assertEqual(Namespace('http://example.org/', fsep='/').foo, 'http://example.org/foo')
+ self.assertEqual(Namespace('http://example.org/', fsep='/').bar, 'http://example.org/bar')
+ self.assertEqual(Namespace('http://example.org', fsep='/').foo, 'http://example.org/foo')
+ self.assertEqual(Namespace('http://example.org', fsep='/').bar, 'http://example.org/bar')
+ self.assertEqual(Namespace('http://example.org#', fsep='/').foo, 'http://example.org#/foo')
+ self.assertEqual(Namespace('http://example.org#', fsep='/').bar, 'http://example.org#/bar')
+ self.assertEqual(Namespace('http://example.org/me#').foo, 'http://example.org/me#foo')
+ self.assertEqual(Namespace('http://example.org/me#').bar, 'http://example.org/me#bar')
+
+ def test_getitem(self):
+ self.assertEqual(Namespace('http://example.org')['foo'], 'http://example.org#foo')
+ self.assertEqual(Namespace('http://example.org')['bar'], 'http://example.org#bar')
+ self.assertEqual(Namespace('http://example.org', fsep='/')['foo'], 'http://example.org/foo')
+ self.assertEqual(Namespace('http://example.org', fsep='/')['bar'], 'http://example.org/bar')
+ self.assertEqual(Namespace('http://example.org/me#')['foo'], 'http://example.org/me#foo')
+ self.assertEqual(Namespace('http://example.org/me#')['bar'], 'http://example.org/me#bar')
+
+ def test_add(self):
+ self.assertEqual(Namespace('http://example.org') + 'foo', Namespace('http://example.org/foo'))
+ self.assertEqual(Namespace('http://example.org', psep='.') + 'foo', Namespace('http://example.org.foo', psep='.'))
+ self.assertEqual(Namespace('http://example.org') + 'foo' + 'bar', Namespace('http://example.org/foo/bar'))
+ # can add URIs
+ self.assertEqual(Namespace('http://example.org') + URI('foo'), Namespace('http://example.org/foo'))
+ # can only add strings
+ self.assertRaises(TypeError, operator.add, Namespace('http://example.org'), 1234)
+ self.assertRaises(TypeError, operator.add, Namespace('http://example.org'), Namespace('http://example.com'))
+
+
+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(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, ., /)')
+ self.assertEqual(repr(ClosedNamespace('http://example.org', psep='.')), 'ClosedNamespace(http://example.org, #, .)')
+ # comparison
+ class Foo(ClosedNamespace): pass
+ self.assertEqual(ClosedNamespace('http://example.org'), ClosedNamespace('http://example.org#'))
+ self.assertEqual(ClosedNamespace('http://example.org'), ClosedNamespace('http://example.org'))
+ self.assertEqual(ClosedNamespace('http://example.org'), ClosedNamespace('http://example.org/'))
+ self.assertEqual(ClosedNamespace('http://example.org/', 'foo', 'bar'), ClosedNamespace('http://example.org/', 'foo', 'bar'))
+ self.assertNotEqual(ClosedNamespace('http://example.org/', 'foo'), ClosedNamespace('http://example.org/', 'bar'))
+ self.assertNotEqual(ClosedNamespace('http://example.org/'), Foo('http://example.org/'))
+ self.assertNotEqual(Foo('http://example.org/'), ClosedNamespace('http://example.org/'))
+ # hashing
+ self.assertEqual(hash(ClosedNamespace('http://example.org')), hash(ClosedNamespace('http://example.org')))
+ self.assertEqual(hash(ClosedNamespace('http://example.org')), hash(ClosedNamespace('http://example.org/')))
+ self.assertEqual(hash(ClosedNamespace('http://example.org')), hash(ClosedNamespace('http://example.org#')))
+ self.assertEqual(hash(ClosedNamespace('http://example.org/', 'foo', 'bar')), hash(ClosedNamespace('http://example.org/', 'foo', 'bar')))
+ self.assertNotEqual(hash(ClosedNamespace('http://example.org/', 'foo')), hash(ClosedNamespace('http://example.org/', 'bar')))
+ self.assertNotEqual(hash(ClosedNamespace('http://example.org/')), hash(Foo('http://example.org/')))
+ self.assertNotEqual(hash(Foo('http://example.org/')), hash(ClosedNamespace('http://example.org/')))
+
+ def test_getattr(self):
+ self.assertEqual(ClosedNamespace('http://example.org/', 'foo', 'bar').foo, 'http://example.org#foo')
+ self.assertEqual(ClosedNamespace('http://example.org/', 'bar', 'bar').bar, 'http://example.org#bar')
+ self.assertEqual(ClosedNamespace('http://example.org/me#', 'foo', 'bar').foo, 'http://example.org/me#foo')
+ self.assertEqual(ClosedNamespace('http://example.org/me#', 'foo', 'bar').bar, 'http://example.org/me#bar')
+ self.assertRaises(KeyError, getattr, ClosedNamespace('http://example.org/', 'bar', 'bar'), 'foobar')
+ self.assertRaises(KeyError, getattr, ClosedNamespace('http://example.org#', 'bar', 'bar'), 'foobar')
+
+ def test_getitem(self):
+ self.assertEqual(ClosedNamespace('http://example.org/', 'foo', 'bar')['foo'], 'http://example.org#foo')
+ self.assertEqual(ClosedNamespace('http://example.org/', 'foo', 'bar')['bar'], 'http://example.org#bar')
+ self.assertEqual(ClosedNamespace('http://example.org/me#', 'foo', 'bar')['foo'], 'http://example.org/me#foo')
+ self.assertEqual(ClosedNamespace('http://example.org/me#', 'foo', 'bar')['bar'], 'http://example.org/me#bar')
+ self.assertRaises(KeyError, ClosedNamespace('http://example.org/', 'bar', 'bar').__getitem__, 'foobar')
+ self.assertRaises(KeyError, ClosedNamespace('http://example.org#', 'bar', 'bar').__getitem__, 'foobar')
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/schema/__init__.py b/test/schema/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/schema/__init__.py
diff --git a/test/schema/test_schema.py b/test/schema/test_schema.py
new file mode 100644
index 0000000..888cdca
--- /dev/null
+++ b/test/schema/test_schema.py
@@ -0,0 +1,745 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import operator
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.schema import types
+from bsfs.utils import errors
+
+# objects to test
+from bsfs.schema.schema import Schema
+
+
+## code ##
+
+class TestSchema(unittest.TestCase):
+
+ def setUp(self):
+ self.schema_str = '''
+ 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:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+ bsfs:Unused rdfs:subClassOf bsfs:Node .
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:group rdfs:subClassOf bse:tag ;
+ rdfs:domain bsfs:Image ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:string ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ '''
+ # 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.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]
+
+ # 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.predicates = [self.p_root, self.p_tag, self.p_group, self.p_comment]
+
+ def test_construction(self):
+ # 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.predicates()), set(self.predicates))
+
+ # predicates, nodes, and literals are respected
+ schema = Schema(self.predicates, self.nodes, self.literals)
+ self.assertSetEqual(set(schema.nodes()), set(self.nodes))
+ self.assertSetEqual(set(schema.literals()), set(self.literals))
+ self.assertSetEqual(set(schema.predicates()), set(self.predicates))
+
+ # nodes are complete (w/o unused)
+ schema = Schema(self.predicates, None, self.literals)
+ self.assertSetEqual(set(schema.nodes()), {self.n_root, self.n_ent, self.n_img, self.n_tag})
+ schema = Schema(self.predicates, [], self.literals)
+ self.assertSetEqual(set(schema.nodes()), {self.n_root, self.n_ent, self.n_img, self.n_tag})
+ schema = Schema(self.predicates, [self.n_img, self.n_tag], self.literals)
+ self.assertSetEqual(set(schema.nodes()), {self.n_root, self.n_ent, self.n_img, self.n_tag})
+ schema = Schema(self.predicates, [self.n_unused], self.literals)
+ self.assertSetEqual(set(schema.nodes()), set(self.nodes))
+
+ # literals are complete
+ schema = Schema(self.predicates, self.nodes, None)
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ schema = Schema(self.predicates, self.nodes, [])
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ schema = Schema(self.predicates, self.nodes, [self.l_string])
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ schema = Schema(self.predicates, self.nodes, [self.l_integer])
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_integer})
+ 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())
+ 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)
+ self.assertSetEqual(set(schema.predicates()), set(self.predicates))
+
+ # node uris must be unique
+ self.assertRaises(errors.ConsistencyError, Schema, self.predicates,
+ self.nodes + [types.Node(ns.bsfs.Entity, None)], self.literals)
+ self.assertRaises(errors.ConsistencyError, Schema, self.predicates,
+ self.nodes + [types.Node(ns.bsfs.Entity, types.Node(ns.bsfs.Foo, None))], self.literals)
+ self.assertRaises(errors.ConsistencyError, Schema, self.predicates,
+ self.nodes + [types.Node(ns.bsfs.Entity, self.n_img)], self.literals)
+ self.assertRaises(errors.ConsistencyError, Schema, self.predicates,
+ [types.Node(ns.bsfs.Entity, self.n_img)], self.literals)
+
+ # literal uris must be unique
+ self.assertRaises(errors.ConsistencyError, Schema, self.predicates, self.nodes,
+ self.literals + [types.Literal(ns.xsd.string, None)])
+ self.assertRaises(errors.ConsistencyError, Schema, self.predicates, self.nodes,
+ self.literals + [types.Literal(ns.xsd.string, types.Literal(ns.bsfs.Foo, None))])
+ self.assertRaises(errors.ConsistencyError, Schema, self.predicates, self.nodes,
+ self.literals + [types.Literal(ns.xsd.string, self.l_integer)])
+ self.assertRaises(errors.ConsistencyError, Schema, self.predicates, self.nodes,
+ [types.Literal(ns.xsd.string, self.l_integer)])
+
+ # predicate uris must be unique
+ self.assertRaises(errors.ConsistencyError, Schema,
+ self.predicates + [types.Predicate(ns.bse.tag, self.p_root, self.n_root, self.n_tag, False)])
+ self.assertRaises(errors.ConsistencyError, Schema,
+ self.predicates + [types.Predicate(ns.bse.tag, self.p_root, self.n_ent, self.n_img, False)])
+ self.assertRaises(errors.ConsistencyError, Schema,
+ self.predicates + [types.Predicate(ns.bse.tag, self.p_root, self.n_ent, self.n_tag, True)])
+ self.assertRaises(errors.ConsistencyError, Schema,
+ self.predicates + [types.Predicate(ns.bse.tag, None, self.n_ent, self.n_tag, False)])
+
+ # uris must be unique across nodes, literals, and predicates
+ 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)})
+ 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)}, {})
+ 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)})
+
+ def test_str(self):
+ 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([], [], [])')
+ n = [ns.bsfs.Entity, ns.bsfs.Image, ns.bsfs.Node, ns.bsfs.Tag, ns.bsfs.Unused]
+ l = [ns.bsfs.Literal, ns.xsd.boolean, ns.xsd.integer, ns.xsd.string]
+ p = [ns.bse.comment, ns.bse.group, ns.bse.tag, ns.bsfs.Predicate]
+ self.assertEqual(repr(Schema(self.predicates, self.nodes, self.literals)), f'Schema({n}, {l}, {p})')
+
+ def test_equality(self):
+ schema = Schema(self.predicates, self.nodes, self.literals)
+ # instance is equal to itself
+ self.assertEqual(schema, schema)
+ self.assertEqual(hash(schema), hash(schema))
+ # instance is equal to a clone
+ self.assertEqual(schema, Schema(self.predicates, self.nodes, self.literals))
+ self.assertEqual(hash(schema), hash(Schema(self.predicates, self.nodes, self.literals)))
+ # equality respects nodes
+ self.assertNotEqual(schema,
+ Schema(self.predicates, [self.n_root, self.n_ent, self.n_img, self.n_tag], self.literals))
+ self.assertNotEqual(hash(schema),
+ hash(Schema(self.predicates, [self.n_root, self.n_ent, self.n_img, self.n_tag], self.literals)))
+ self.assertNotEqual(schema,
+ Schema(self.predicates, self.nodes + [types.Node(ns.bsfs.Document, self.n_ent)], self.literals))
+ self.assertNotEqual(hash(schema),
+ hash(Schema(self.predicates, self.nodes + [types.Node(ns.bsfs.Document, self.n_ent)], self.literals)))
+ # equality respects literals
+ self.assertNotEqual(schema,
+ Schema(self.predicates, self.nodes, [self.l_root, self.l_string, self.l_integer]))
+ self.assertNotEqual(hash(schema),
+ hash(Schema(self.predicates, self.nodes, [self.l_root, self.l_string, self.l_integer])))
+ self.assertNotEqual(schema,
+ Schema(self.predicates, self.nodes, self.literals + [types.Literal(ns.xsd.number, self.l_root)]))
+ self.assertNotEqual(hash(schema),
+ hash(Schema(self.predicates, self.nodes, self.literals + [types.Literal(ns.xsd.number, self.l_root)])))
+ # equality respects predicates
+ self.assertNotEqual(schema,
+ Schema([self.p_group, self.p_tag, self.p_root], self.nodes, self.literals))
+ 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))
+ 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)))
+
+ 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)
+
+ # can only compare schema to other schema
+ # <
+ self.assertRaises(TypeError, operator.lt, Schema({p_foo, p_bar}), 'hello world')
+ self.assertRaises(TypeError, operator.lt, Schema({p_foo, p_bar}), 1234)
+ self.assertRaises(TypeError, operator.lt, Schema({p_foo, p_bar}), p_foo)
+ self.assertRaises(TypeError, operator.lt, Schema({p_foo, p_bar}), Foo())
+ # <=
+ self.assertRaises(TypeError, operator.le, Schema({p_foo, p_bar}), 'hello world')
+ self.assertRaises(TypeError, operator.le, Schema({p_foo, p_bar}), 1234)
+ self.assertRaises(TypeError, operator.le, Schema({p_foo, p_bar}), p_foo)
+ self.assertRaises(TypeError, operator.le, Schema({p_foo, p_bar}), Foo())
+ # >
+ self.assertRaises(TypeError, operator.gt, Schema({p_foo, p_bar}), 'hello world')
+ self.assertRaises(TypeError, operator.gt, Schema({p_foo, p_bar}), 1234)
+ self.assertRaises(TypeError, operator.gt, Schema({p_foo, p_bar}), p_foo)
+ self.assertRaises(TypeError, operator.gt, Schema({p_foo, p_bar}), Foo())
+ # >=
+ self.assertRaises(TypeError, operator.ge, Schema({p_foo, p_bar}), 'hello world')
+ self.assertRaises(TypeError, operator.ge, Schema({p_foo, p_bar}), 1234)
+ self.assertRaises(TypeError, operator.ge, Schema({p_foo, p_bar}), p_foo)
+ self.assertRaises(TypeError, operator.ge, Schema({p_foo, p_bar}), Foo())
+
+ # a schema is a subset of itself
+ self.assertTrue(operator.le(Schema({self.p_tag}), Schema({self.p_tag})))
+ # a schema is a superset of itself
+ self.assertTrue(operator.ge(Schema({self.p_tag}), Schema({self.p_tag})))
+ # a schema is not a true subset of itself
+ self.assertFalse(operator.lt(Schema({self.p_tag}), Schema({self.p_tag})))
+ # a schema is not a true superset of itself
+ self.assertFalse(operator.gt(Schema({self.p_tag}), Schema({self.p_tag})))
+
+ # subset considers predicates
+ self.assertTrue(operator.lt(Schema({p_foo}), Schema({p_foo, p_bar})))
+ self.assertTrue(operator.lt(Schema({p_foo}), Schema({p_sub})))
+ self.assertFalse(operator.lt(Schema({p_foo}), Schema({p_bar})))
+ # subset considers nodes
+ self.assertTrue(operator.lt(Schema({self.p_tag}), Schema({self.p_tag}, {self.n_unused})))
+ self.assertFalse(operator.lt(Schema({self.p_tag}, {self.n_unused}), Schema({self.p_tag})))
+ # subset considers literals
+ self.assertTrue(operator.lt(Schema({self.p_tag}), Schema({self.p_tag}, {}, {self.l_unused})))
+ self.assertFalse(operator.lt(Schema({self.p_tag}, {}, {self.l_unused}), Schema({self.p_tag})))
+ # subset considers differences in predicates and nodes
+ 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})))
+ # 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.assertFalse(operator.lt(Schema({self.p_comment}), Schema({self.p_tag})))
+ self.assertFalse(operator.le(Schema({self.p_comment}), Schema({self.p_tag})))
+
+ # superset considers predicates
+ self.assertTrue(operator.gt(Schema({p_foo, p_bar}), Schema({p_foo})))
+ self.assertTrue(operator.gt(Schema({p_sub}), Schema({p_foo})))
+ self.assertFalse(operator.gt(Schema({p_foo}), Schema({p_bar})))
+ # superset considers nodes
+ self.assertTrue(operator.gt(Schema({self.p_tag}, {self.n_unused}), Schema({self.p_tag})))
+ self.assertFalse(operator.gt(Schema({self.p_tag}), Schema({self.p_tag}, {self.n_unused})))
+ # superset considers literals
+ self.assertTrue(operator.gt(Schema({self.p_tag}, {}, {self.l_unused}), Schema({self.p_tag})))
+ self.assertFalse(operator.gt(Schema({self.p_tag}), Schema({self.p_tag}, {}, {self.l_unused})))
+ # superset considers differences in predicates and nodes
+ 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()))
+ # 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.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.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.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.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.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.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.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.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.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.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.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.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.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({}, {}, {
+ types.Literal(ns.xsd.integer, types.Literal(ns.xsd.number, types.Literal(ns.bsfs.Literal, None)))})))
+
+
+
+ def test_diff(self):
+ # difference can be empty
+ diff = Schema({self.p_tag}).diff(Schema({self.p_group}))
+ self.assertSetEqual(set(diff.nodes), set())
+ self.assertSetEqual(set(diff.literals), set())
+ self.assertSetEqual(set(diff.predicates), set())
+
+ # difference contains predicates from the LHS
+ diff = Schema({self.p_group}).diff(Schema({self.p_tag}))
+ self.assertSetEqual(set(diff.nodes), {self.n_img})
+ self.assertSetEqual(set(diff.literals), set())
+ self.assertSetEqual(set(diff.predicates), {self.p_group})
+
+ # 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.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.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.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.predicates), {self.p_comment})
+ # __sub__ only accepts Schema instances
+ class Foo(): pass
+ self.assertRaises(TypeError, operator.sub, Schema({self.p_comment}, {self.n_unused}, {self.l_unused}), 1234)
+ self.assertRaises(TypeError, operator.sub, Schema({self.p_comment}, {self.n_unused}, {self.l_unused}), 'hello world')
+ self.assertRaises(TypeError, operator.sub, Schema({self.p_comment}, {self.n_unused}, {self.l_unused}), Foo())
+
+ def test_consistent_with(self):
+ # argument must be a schema
+ class Foo(): pass
+ self.assertRaises(TypeError, Schema([]).consistent_with, 1234)
+ self.assertRaises(TypeError, Schema([]).consistent_with, 'hello world')
+ self.assertRaises(TypeError, Schema([]).consistent_with, Foo())
+
+ # node consistency
+ self.assertTrue(Schema([], {self.n_ent, self.n_tag, self.n_unused}).consistent_with(
+ Schema(self.predicates)))
+ self.assertFalse(Schema([], {types.Node(ns.bsfs.Entity, None)}).consistent_with(
+ Schema(self.predicates)))
+ # order doesn't matter
+ self.assertTrue(Schema(self.predicates).consistent_with(
+ Schema([], {self.n_ent, self.n_tag, self.n_unused})))
+
+ # literal consistency
+ self.assertTrue(Schema([], [], {self.l_string, self.l_unused}).consistent_with(
+ Schema(self.predicates)))
+ self.assertFalse(Schema([], [], {types.Literal(ns.xsd.string, None)}).consistent_with(
+ Schema(self.predicates)))
+ # order doesn't matter
+ self.assertTrue(Schema(self.predicates).consistent_with(
+ Schema([], [], {self.l_string, self.l_unused})))
+
+ # predicate consistency
+ self.assertTrue(Schema({self.p_tag}).consistent_with(
+ Schema(self.predicates)))
+ self.assertFalse(Schema({types.Predicate(ns.bse.tag, None, self.n_root, self.n_root, False)}).consistent_with(
+ Schema(self.predicates)))
+ # order doesn't matter
+ self.assertTrue(Schema(self.predicates).consistent_with(
+ Schema({self.p_tag})))
+
+ # global consistency
+ self.assertFalse(Schema({types.Predicate(ns.bsfs.Entity, None, self.n_root, self.n_root, False)}).consistent_with(
+ Schema(self.predicates)))
+ self.assertFalse(Schema([], {types.Node(ns.xsd.string, None)}).consistent_with(
+ Schema(self.predicates)))
+ self.assertFalse(Schema([], [], {types.Literal(ns.bsfs.Entity, None)}).consistent_with(
+ Schema(self.predicates)))
+
+
+ def test_union(self):
+ # must provide at least one schema
+ self.assertRaises(TypeError, Schema.Union)
+
+ # can pass schemas as list
+ self.assertEqual(Schema.Union([Schema({self.p_tag})]), Schema({self.p_tag}))
+ self.assertEqual(Schema.Union([Schema({self.p_tag}), Schema({self.p_comment})]),
+ Schema({self.p_tag, self.p_comment}))
+
+ # can pass schemas as arguments
+ self.assertEqual(Schema.Union(Schema({self.p_tag})), Schema({self.p_tag}))
+ self.assertEqual(Schema.Union(Schema({self.p_tag}), Schema({self.p_comment})),
+ Schema({self.p_tag, self.p_comment}))
+
+ # cannot mix the two argument passing styles
+ self.assertRaises(TypeError, Schema.Union, [Schema(self.predicates)], Schema(self.predicates))
+
+ # all arguments must be Schema instances
+ self.assertRaises(TypeError, Schema.Union, Schema(self.predicates), 1234)
+ self.assertRaises(TypeError, Schema.Union, Schema(self.predicates), 1234, Schema(self.predicates))
+ self.assertRaises(TypeError, Schema.Union, Schema(self.predicates), 'hello world')
+
+ # Union merges predicates, nodes, and literals
+ self.assertEqual(Schema.Union(
+ Schema({self.p_comment}, {self.n_unused}, {}),
+ Schema({self.p_group}, {self.n_img}, {self.l_unused})),
+ Schema({self.p_comment, self.p_group}, {self.n_img, self.n_unused}, {self.l_unused}))
+
+ # Union does not accept inconsistent nodes
+ self.assertRaises(errors.ConsistencyError, Schema.Union, Schema(self.predicates),
+ Schema({}, {types.Node(ns.bsfs.Entity, None)}))
+ self.assertRaises(errors.ConsistencyError, Schema.Union, Schema({}, {self.n_ent}),
+ Schema({}, {types.Node(ns.bsfs.Entity, None)}))
+ self.assertRaises(errors.ConsistencyError, Schema.Union, Schema({}, {self.n_ent}),
+ Schema({}, {}, {types.Literal(ns.bsfs.Entity, None)}))
+
+ # Union does not accept inconsistent literals
+ self.assertRaises(errors.ConsistencyError, Schema.Union, Schema(self.predicates),
+ Schema({}, {}, {types.Literal(ns.xsd.string, None)}))
+ self.assertRaises(errors.ConsistencyError, Schema.Union, Schema({}, {}, {self.l_string}),
+ Schema({}, {}, {types.Literal(ns.xsd.string, None)}))
+ self.assertRaises(errors.ConsistencyError, Schema.Union, Schema({}, {}, {self.l_string}),
+ Schema({}, {types.Node(ns.xsd.string, None)}))
+
+ # Union does not accept inconsistent predicates
+ self.assertRaises(errors.ConsistencyError, Schema.Union, Schema({self.p_tag}),
+ Schema({types.Predicate(ns.bse.tag, None, self.n_ent, self.n_tag, False)}))
+ self.assertRaises(errors.ConsistencyError, Schema.Union, Schema({self.p_tag}),
+ Schema({}, {types.Node(ns.bse.tag, None)}))
+
+ # union is an alias for Union
+ self.assertEqual(Schema({self.p_comment}, {self.n_unused}, {}).union(
+ Schema({self.p_group}, {self.n_img}, {self.l_unused})),
+ Schema({self.p_comment, self.p_group}, {self.n_img, self.n_unused}, {self.l_unused}))
+ # union only accepts Schema instances
+ class Foo(): pass
+ self.assertRaises(TypeError, Schema({self.p_comment}, {self.n_unused}, {}).union, 1234)
+ self.assertRaises(TypeError, Schema({self.p_comment}, {self.n_unused}, {}).union, 'hello world')
+ self.assertRaises(TypeError, Schema({self.p_comment}, {self.n_unused}, {}).union, Foo())
+
+ # __add__ is an alias for Union
+ self.assertEqual(Schema({self.p_comment}, {self.n_unused}, {}) + Schema({self.p_group}, {self.n_img}, {self.l_unused}),
+ Schema({self.p_comment, self.p_group}, {self.n_img, self.n_unused}, {self.l_unused}))
+ # __add__ only accepts Schema instances
+ class Foo(): pass
+ self.assertRaises(TypeError, operator.add, Schema({self.p_comment}, {self.n_unused}, {}), 1234)
+ self.assertRaises(TypeError, operator.add, Schema({self.p_comment}, {self.n_unused}, {}), 'hello world')
+ self.assertRaises(TypeError, operator.add, Schema({self.p_comment}, {self.n_unused}, {}), Foo())
+
+ # __or__ is an alias for Union
+ self.assertEqual(Schema({self.p_comment}, {self.n_unused}, {}) | Schema({self.p_group}, {self.n_img}, {self.l_unused}),
+ Schema({self.p_comment, self.p_group}, {self.n_img, self.n_unused}, {self.l_unused}))
+ # __or__ only accepts Schema instances
+ class Foo(): pass
+ self.assertRaises(TypeError, operator.or_, Schema({self.p_comment}, {self.n_unused}, {}), 1234)
+ self.assertRaises(TypeError, operator.or_, Schema({self.p_comment}, {self.n_unused}, {}), 'hello world')
+ self.assertRaises(TypeError, operator.or_, Schema({self.p_comment}, {self.n_unused}, {}), Foo())
+
+ def test_type_getters(self):
+ schema = Schema(self.predicates, self.nodes, self.literals)
+ # nodes
+ self.assertEqual(self.n_root, schema.node(ns.bsfs.Node))
+ self.assertEqual(self.n_ent, schema.node(ns.bsfs.Entity))
+ self.assertEqual(self.n_img, schema.node(ns.bsfs.Image))
+ self.assertRaises(KeyError, schema.node, ns.bsfs.Document)
+ self.assertRaises(KeyError, schema.node, self.n_root)
+ # literals
+ self.assertEqual(self.l_root, schema.literal(ns.bsfs.Literal))
+ self.assertEqual(self.l_string, schema.literal(ns.xsd.string))
+ self.assertEqual(self.l_integer, schema.literal(ns.xsd.integer))
+ self.assertRaises(KeyError, schema.literal, ns.xsd.number)
+ self.assertRaises(KeyError, schema.literal, self.l_root)
+ # predicates
+ self.assertEqual(self.p_root, schema.predicate(ns.bsfs.Predicate))
+ self.assertEqual(self.p_tag, schema.predicate(ns.bse.tag))
+ self.assertEqual(self.p_group, schema.predicate(ns.bse.group))
+ self.assertRaises(KeyError, schema.predicate, ns.bse.mimetype)
+ self.assertRaises(KeyError, schema.predicate, self.p_root)
+
+ def test_list_getters(self):
+ schema = Schema(self.predicates, self.nodes, self.literals)
+ self.assertSetEqual(set(self.nodes), set(schema.nodes()))
+ self.assertSetEqual(set(self.literals), set(schema.literals()))
+ self.assertSetEqual(set(self.predicates), set(schema.predicates()))
+
+ def test_has(self):
+ schema = Schema(self.predicates, self.nodes, self.literals)
+ # nodes
+ self.assertTrue(schema.has_node(ns.bsfs.Node))
+ self.assertTrue(schema.has_node(ns.bsfs.Entity))
+ self.assertTrue(schema.has_node(ns.bsfs.Image))
+ self.assertFalse(schema.has_node(ns.bsfs.Document))
+ self.assertFalse(schema.has_node(self.n_root))
+ # literals
+ self.assertTrue(schema.has_literal(ns.bsfs.Literal))
+ self.assertTrue(schema.has_literal(ns.xsd.string))
+ self.assertTrue(schema.has_literal(ns.xsd.integer))
+ self.assertFalse(schema.has_literal(ns.xsd.number))
+ self.assertFalse(schema.has_literal(self.l_root))
+ # predicates
+ self.assertTrue(schema.has_predicate(ns.bsfs.Predicate))
+ self.assertTrue(schema.has_predicate(ns.bse.tag))
+ self.assertTrue(schema.has_predicate(ns.bse.group))
+ 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__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/schema/test_types.py b/test/schema/test_types.py
new file mode 100644
index 0000000..4a49e6e
--- /dev/null
+++ b/test/schema/test_types.py
@@ -0,0 +1,225 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import operator
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.utils import errors
+
+# objects to test
+from bsfs.schema.types import _Type, _Vertex, Node, Literal, Predicate
+
+
+## code ##
+
+class TestType(unittest.TestCase):
+ def test_parents(self):
+ # create some types
+ fst = _Type('First')
+ snd = _Type('Second', fst)
+ trd = _Type('Third', snd)
+ frd = _Type('Fourth', trd)
+ # check parents
+ self.assertListEqual(list(fst.parents()), [])
+ self.assertListEqual(list(snd.parents()), [fst])
+ self.assertListEqual(list(trd.parents()), [snd, fst])
+ self.assertListEqual(list(frd.parents()), [trd, snd, fst])
+
+ def test_essentials(self):
+ # type w/o parent
+ self.assertEqual(str(_Type('Foo')), '_Type(Foo)')
+ self.assertEqual(repr(_Type('Foo')), '_Type(Foo, None)')
+ # type w/ parent
+ self.assertEqual(str(_Type('Foo', _Type('Bar'))), '_Type(Foo)')
+ self.assertEqual(repr(_Type('Foo', _Type('Bar'))), '_Type(Foo, _Type(Bar, None))')
+ # subtype w/o parent
+ class SubType(_Type): pass
+ self.assertEqual(str(SubType('Foo')), 'SubType(Foo)')
+ self.assertEqual(repr(SubType('Foo')), 'SubType(Foo, None)')
+ # subtype w/ parent
+ self.assertEqual(str(SubType('Foo', SubType('Bar'))), 'SubType(Foo)')
+ self.assertEqual(repr(SubType('Foo', SubType('Bar'))), 'SubType(Foo, SubType(Bar, None))')
+ # subtype and type mixed
+ self.assertEqual(str(SubType('Foo', _Type('Bar'))), 'SubType(Foo)')
+ self.assertEqual(repr(SubType('Foo', _Type('Bar'))), 'SubType(Foo, _Type(Bar, None))')
+ 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):
+ # callee is used as parent
+ self.assertEqual(_Type('First').get_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'))))
+ # type persists
+ class Foo(_Type): pass
+ self.assertEqual(Foo('First').get_child('Second'), Foo('Second', Foo('First')))
+
+ def test_equality(self):
+ # equality depends on uri
+ self.assertEqual(_Type('Foo'), _Type('Foo'))
+ self.assertEqual(hash(_Type('Foo')), hash(_Type('Foo')))
+ self.assertNotEqual(_Type('Foo'), _Type('Bar'))
+ self.assertNotEqual(hash(_Type('Foo')), hash(_Type('Bar')))
+ # comparison is case-sensitive
+ self.assertNotEqual(_Type('FOO'), _Type('foo'))
+ self.assertNotEqual(hash(_Type('FOO')), hash(_Type('foo')))
+ # comparison respects type
+ class Foo(_Type): pass
+ self.assertNotEqual(_Type('Foo'), Foo('Foo'))
+ self.assertNotEqual(hash(_Type('Foo')), hash(Foo('Foo')))
+ # comparison respects parent
+ self.assertNotEqual(_Type('Foo', _Type('Bar')), _Type('Foo'))
+ self.assertNotEqual(hash(_Type('Foo', _Type('Bar'))), hash(_Type('Foo')))
+
+ def test_order(self):
+ # create some types.
+ vehicle = _Type('Vehicle')
+ twowheel = _Type('Two-wheel', vehicle)
+ bike = _Type('Bike', twowheel)
+ bicycle = _Type('Bicycle', twowheel)
+ # two-wheel is equivalent to itself
+ self.assertFalse(twowheel == vehicle)
+ self.assertTrue(twowheel == twowheel)
+ self.assertFalse(twowheel == bicycle)
+ # two-wheel is a true subclass of Vehicle
+ self.assertTrue(twowheel < vehicle)
+ self.assertFalse(twowheel < twowheel)
+ self.assertFalse(twowheel < bicycle)
+ # two-wheel is a subclass of itself and Vehicle
+ self.assertTrue(twowheel <= vehicle)
+ self.assertTrue(twowheel <= twowheel)
+ self.assertFalse(twowheel <= bicycle)
+ # two-wheel is a true superclass of Bicycle
+ self.assertFalse(twowheel > vehicle)
+ self.assertFalse(twowheel > twowheel)
+ self.assertTrue(twowheel > bicycle)
+ # two-wheel is a superclass of itself and Bicycle
+ self.assertFalse(twowheel >= vehicle)
+ self.assertTrue(twowheel >= twowheel)
+ self.assertTrue(twowheel >= bicycle)
+ # analoguous to sets, this is not a total order
+ self.assertFalse(bike <= bicycle)
+ self.assertFalse(bike < bicycle)
+ self.assertFalse(bike > bicycle)
+ self.assertFalse(bike >= bicycle)
+ self.assertFalse(bike == bicycle)
+ 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)
+ # 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)
+
+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
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), 1234, True)
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), _Vertex(ns.bsfs.Foo, None), True)
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), _Type(ns.bsfs.Foo, None), True)
+ class Foo(): pass
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), Foo(), True)
+
+ def test_equality(self):
+ 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,
+ domain=n_root,
+ range=None,
+ unique=False,
+ )
+ # instance is equal to itself
+ self.assertEqual(root, root)
+ self.assertEqual(hash(root), hash(root))
+ # 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)))
+ # 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)))
+ # 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)))
+ # 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)))
+ # 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)))
+ # 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)))
+
+ def test_get_child(self):
+ 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,
+ domain=n_root,
+ range=None,
+ unique=False,
+ )
+ tag = Predicate(
+ uri=ns.bsfs.Entity,
+ parent=root,
+ domain=n_ent,
+ range=n_tag,
+ unique=False,
+ )
+
+ # uri is respected
+ self.assertEqual(ns.bse.foo, tag.get_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)
+ # range is respected
+ rng = Node(ns.bsfs.Group, n_tag)
+ self.assertEqual(rng, tag.get_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)
+ # unique is respected
+ self.assertTrue(tag.get_child(ns.bse.foo, unique=True).unique)
+
+ # domain is inherited from parent
+ self.assertEqual(n_ent, tag.get_child(ns.bse.foo).domain)
+ # range is inherited from parent
+ self.assertEqual(n_tag, tag.get_child(ns.bse.foo).range)
+ # uniqueness is inherited from parent
+ self.assertFalse(tag.get_child(ns.bse.foo).unique)
+
+ # domain must be subtype of parent's domain
+ self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, domain=n_root)
+ self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, domain=Node(ns.bsfs.Image, n_root))
+ # range cannot be None
+ self.assertRaises(ValueError, root.get_child, ns.bse.foo)
+ # range must be subtype of parent's range
+ self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, range=n_root)
+ self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, range=Node(ns.bsfs.Image, n_root))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
+
diff --git a/test/triple_store/__init__.py b/test/triple_store/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/triple_store/__init__.py
diff --git a/test/triple_store/test_base.py b/test/triple_store/test_base.py
new file mode 100644
index 0000000..a4b0559
--- /dev/null
+++ b/test/triple_store/test_base.py
@@ -0,0 +1,150 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import unittest
+
+# bsie imports
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.triple_store.base import TripleStoreBase
+
+
+## code ##
+
+class DummyBase(TripleStoreBase):
+ @classmethod
+ def Open(cls, uri, **kwargs):
+ return cls(uri)
+
+ def commit(self):
+ pass
+
+ def rollback(self):
+ pass
+
+ @property
+ def schema(self):
+ pass
+
+ @schema.setter
+ def schema(self, schema):
+ pass
+
+ def exists(self, node_type, guids):
+ pass
+
+ def create(self, node_type, guids):
+ pass
+
+ def set(self, node_type, guids, predicate, values):
+ pass
+
+class DummyStore(DummyBase):
+ pass
+
+class DummyAlternative(DummyBase):
+ pass
+
+
+class TestTripleStoreBase(unittest.TestCase):
+
+ def test_equality(self):
+ # identical instances are equal
+ store = DummyStore.Open(None)
+ self.assertEqual(store, store)
+ self.assertEqual(hash(store), hash(store))
+ store = DummyStore.Open(URI('http://example.com/store'))
+ self.assertEqual(store, store)
+ self.assertEqual(hash(store), hash(store))
+ # in-memory storages are not equal
+ # NOTE: Don't use
+ # >>> self.assertNotEqual(hash(DummyStore(None)), hash(DummyStore(None)))
+ # The two stores are created subsequently since each of them is deleted
+ # right after hashing. Because the two instances never exist at the same
+ # time, their id may (and typically will) be identical.
+ # This only matters when the `id` function is used, i.e. when uri=None.
+ a, b = DummyStore.Open(None), DummyStore.Open(None)
+ self.assertNotEqual(a, b)
+ self.assertNotEqual(hash(a), hash(b))
+ a, b = DummyStore.Open(None), DummyStore.Open(URI('http://example.com/store'))
+ self.assertNotEqual(a, b)
+ self.assertNotEqual(hash(a), hash(b))
+ a, b = DummyStore.Open(URI('http://example.com/store')), DummyStore.Open(None)
+ self.assertNotEqual(a, b)
+ self.assertNotEqual(hash(a), hash(b))
+ a, b = DummyStore.Open(None), DummyStore.Open(URI('http://example.com/alternative'))
+ self.assertNotEqual(a, b)
+ self.assertNotEqual(hash(a), hash(b))
+ # equality respects uri
+ self.assertEqual(
+ DummyStore.Open(URI('http://example.com/store')),
+ DummyStore.Open(URI('http://example.com/store')))
+ self.assertEqual(
+ hash(DummyStore.Open(URI('http://example.com/alternative'))),
+ hash(DummyStore.Open(URI('http://example.com/alternative'))))
+ self.assertNotEqual(
+ DummyStore.Open(URI('http://example.com/store')),
+ DummyStore.Open(URI('http://example.com/alternative')))
+ self.assertNotEqual(
+ DummyStore.Open(URI('http://example.com/store')),
+ hash(DummyStore.Open(URI('http://example.com/alternative'))))
+ # equality respects type
+ self.assertNotEqual(DummyStore.Open(None), None)
+ self.assertNotEqual(hash(DummyStore.Open(None)), hash(None))
+ self.assertNotEqual(DummyStore.Open(None), 'hello world')
+ self.assertNotEqual(hash(DummyStore.Open(None)), hash('hello world'))
+ self.assertNotEqual(DummyStore.Open(None), 1234)
+ self.assertNotEqual(hash(DummyStore.Open(None)), hash(1234))
+ class Foo(): pass
+ f = Foo()
+ self.assertNotEqual(DummyStore.Open(None), f)
+ self.assertNotEqual(hash(DummyStore.Open(None)), hash(f))
+ self.assertNotEqual(
+ DummyStore.Open(None),
+ DummyAlternative.Open(None))
+ self.assertNotEqual(
+ hash(DummyStore.Open(None)),
+ hash(DummyAlternative.Open(None)))
+
+ def test_string_conversion(self):
+ # string conversion respects uri
+ self.assertEqual('DummyStore(uri=http://example.com/store)',
+ str(DummyStore.Open(URI('http://example.com/store'))))
+ self.assertEqual('DummyStore(uri=http://example.com/store)',
+ repr(DummyStore.Open(URI('http://example.com/store'))))
+ self.assertEqual('DummyStore(uri=http://example.com/alternative)',
+ str(DummyStore.Open(URI('http://example.com/alternative'))))
+ self.assertEqual('DummyStore(uri=http://example.com/alternative)',
+ repr(DummyStore.Open(URI('http://example.com/alternative'))))
+ self.assertEqual('DummyStore(uri=None)',
+ str(DummyStore.Open(None)))
+ self.assertEqual('DummyStore(uri=None)',
+ repr(DummyStore.Open(None)))
+ # string conversion respects type
+ self.assertEqual('DummyAlternative(uri=http://example.com/store)',
+ str(DummyAlternative.Open(URI('http://example.com/store'))))
+
+ def test_uri(self):
+ # uri returns correct value
+ self.assertEqual(None,
+ DummyStore.Open(None).uri)
+ self.assertEqual(URI('http://example.com/store'),
+ DummyStore.Open(URI('http://example.com/store')).uri)
+ self.assertEqual(URI('http://example.com/alternative'),
+ DummyStore.Open(URI('http://example.com/alternative')).uri)
+ # persistence respects uri
+ self.assertFalse(DummyStore.Open(None).is_persistent())
+ self.assertTrue(DummyStore.Open(URI('http://example.com/store')).is_persistent())
+ self.assertTrue(DummyStore.Open(URI('http://example.com/alternative')).is_persistent())
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/triple_store/test_sparql.py b/test/triple_store/test_sparql.py
new file mode 100644
index 0000000..8d98749
--- /dev/null
+++ b/test/triple_store/test_sparql.py
@@ -0,0 +1,769 @@
+"""
+
+Part of the bsfs test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import rdflib
+import unittest
+
+# bsie imports
+from bsfs import schema as _schema
+from bsfs.namespace import ns
+from bsfs.utils import errors, URI
+
+# objects to test
+from bsfs.triple_store.sparql import SparqlStore
+
+
+## code ##
+
+class TestSparqlStore(unittest.TestCase):
+ def setUp(self):
+ self.schema = _schema.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 .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:User rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+
+ # non-unique literal
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ # unique literal
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # non-unique node
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ # unique node
+ bse:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:User ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ ''')
+
+ def test_essentials(self):
+ store = SparqlStore.Open()
+ # equality
+ self.assertEqual(store, store)
+ self.assertEqual(hash(store), hash(store))
+ self.assertNotEqual(store, SparqlStore.Open())
+ self.assertNotEqual(hash(store), hash(SparqlStore.Open()))
+ # string conversion
+ self.assertEqual(str(store), 'SparqlStore(uri=None)')
+ self.assertEqual(repr(store), 'SparqlStore(uri=None)')
+ # open
+ self.assertIsInstance(SparqlStore.Open(), SparqlStore)
+
+
+ def test__has_type(self):
+ # setup store
+ store = SparqlStore.Open()
+ store.schema = _schema.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:Entity .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+ bsfs:PDF rdfs:subClassOf bsfs:Document .
+
+ ''')
+ # add some instances
+ store.create(store.schema.node(ns.bsfs.Entity), {URI('http://example.com/me/entity#1234')})
+ store.create(store.schema.node(ns.bsfs.Document), {URI('http://example.com/me/document#1234')})
+ store.create(store.schema.node(ns.bsfs.Image), {URI('http://example.com/me/image#1234')})
+ 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))
+
+ # returns False on inexistent nodes
+ self.assertFalse(store._has_type(URI('http://example.com/me/entity#4321'), store.schema.node(ns.bsfs.Entity)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/document#4321'), store.schema.node(ns.bsfs.Document)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/image#4321'), store.schema.node(ns.bsfs.Image)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/pdf#4321'), store.schema.node(ns.bsfs.PDF)))
+
+ # _has_type checks direct types
+ self.assertTrue(store._has_type(URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Entity)))
+ self.assertTrue(store._has_type(URI('http://example.com/me/document#1234'), store.schema.node(ns.bsfs.Document)))
+ self.assertTrue(store._has_type(URI('http://example.com/me/image#1234'), store.schema.node(ns.bsfs.Image)))
+ self.assertTrue(store._has_type(URI('http://example.com/me/pdf#1234'), store.schema.node(ns.bsfs.PDF)))
+
+ # _has_type checks type hierarchy
+ self.assertFalse(store._has_type(URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Document)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Image)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.PDF)))
+
+ self.assertTrue(store._has_type(URI('http://example.com/me/document#1234'), store.schema.node(ns.bsfs.Entity)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/document#1234'), store.schema.node(ns.bsfs.Image)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/document#1234'), store.schema.node(ns.bsfs.PDF)))
+
+ self.assertTrue(store._has_type(URI('http://example.com/me/image#1234'), store.schema.node(ns.bsfs.Entity)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/image#1234'), store.schema.node(ns.bsfs.Document)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/image#1234'), store.schema.node(ns.bsfs.PDF)))
+
+ self.assertTrue(store._has_type(URI('http://example.com/me/pdf#1234'), store.schema.node(ns.bsfs.Entity)))
+ self.assertTrue(store._has_type(URI('http://example.com/me/pdf#1234'), store.schema.node(ns.bsfs.Document)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/pdf#1234'), store.schema.node(ns.bsfs.Image)))
+
+
+ def test_schema(self):
+ # setup
+ store = SparqlStore.Open()
+ curr = self.schema
+ p_comment = curr.predicate(ns.bse.comment)
+ p_filesize = curr.predicate(ns.bse.filesize)
+ p_tag = curr.predicate(ns.bse.tag)
+ p_author = curr.predicate(ns.bse.author)
+
+ # migrate to an initial schema
+ store.schema = curr
+ # store has migrated
+ self.assertEqual(store.schema, curr)
+
+ # add some instances
+ ent_ids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ tag_ids = {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')}
+ store.create(curr.node(ns.bsfs.Entity), ent_ids)
+ store.create(curr.node(ns.bsfs.Tag), tag_ids)
+ store.create(curr.node(ns.bsfs.User), {URI('http://example.com/me')})
+ # add some triples
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_comment, {'foo', 'bar'})
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_filesize, {1234})
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_tag,
+ {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_author,
+ {URI('http://example.com/me')})
+ # check instances
+ instances = {
+ # node instances
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.User)),
+ # comments
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ # filesize
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ # tags
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ # author
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me')),
+ }
+ self.assertSetEqual(set(store._graph), instances)
+
+ # add some classes to the schema
+ curr = curr + _schema.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#>
+ prefix bst: <http://bsfs.ai/schema/Tag#>
+ prefix bsc: <http://bsfs.ai/schema/Collection#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:Collection rdfs:subClassOf bsfs:Node .
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+
+ # literal
+ bse:shared rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:boolean ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # node
+ bse:partOf rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Collection ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ # predicates across auxiliary node classes
+ bst:usedIn rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Collection ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bsc:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Collection ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bst:principal rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ ''')
+ # store migrated to the new schema
+ store.schema = curr
+ self.assertEqual(store.schema, curr)
+ # instances have not changed
+ self.assertSetEqual(set(store._graph), instances)
+ # add some instances of the new classes
+ p_partOf = curr.predicate(ns.bse.partOf)
+ p_shared = curr.predicate(ns.bse.shared)
+ p_usedIn = curr.predicate('http://bsfs.ai/schema/Tag#usedIn')
+ p_ctag = curr.predicate('http://bsfs.ai/schema/Collection#tag')
+ p_principal = curr.predicate('http://bsfs.ai/schema/Tag#principal')
+ store.create(curr.node(ns.bsfs.Collection), {URI('http://example.com/me/collection#1234'), URI('http://example.com/me/collection#4321')})
+ # add some more triples
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_shared, {True})
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_partOf,
+ {URI('http://example.com/me/collection#1234'), URI('http://example.com/me/collection#4321')})
+ store.set(curr.node(ns.bsfs.Tag), {URI('http://example.com/me/tag#1234')}, p_usedIn,
+ {URI('http://example.com/me/collection#1234')})
+ store.set(curr.node(ns.bsfs.Collection), {URI('http://example.com/me/collection#4321')}, p_ctag,
+ {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ store.set(curr.node(ns.bsfs.Tag), {URI('http://example.com/me/tag#1234')}, p_principal,
+ {URI('http://example.com/me/collection#1234')})
+ # new instances are now in the graph
+ self.assertSetEqual(set(store._graph), instances | {
+ # collections
+ (rdflib.URIRef('http://example.com/me/collection#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Collection)),
+ (rdflib.URIRef('http://example.com/me/collection#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Collection)),
+ # partOf
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_partOf.uri), rdflib.URIRef('http://example.com/me/collection#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_partOf.uri), rdflib.URIRef('http://example.com/me/collection#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_partOf.uri), rdflib.URIRef('http://example.com/me/collection#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_partOf.uri), rdflib.URIRef('http://example.com/me/collection#4321')),
+ # shared
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_shared.uri), rdflib.Literal('true', datatype=rdflib.XSD.boolean)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_shared.uri), rdflib.Literal('true', datatype=rdflib.XSD.boolean)),
+ # auxiliary node connections
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(p_usedIn.uri), rdflib.URIRef('http://example.com/me/collection#1234')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(p_principal.uri), rdflib.URIRef('http://example.com/me/collection#1234')),
+ (rdflib.URIRef('http://example.com/me/collection#4321'), rdflib.URIRef(p_ctag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/collection#4321'), rdflib.URIRef(p_ctag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ })
+
+
+ # remove some classes from the schema
+ curr = _schema.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#>
+ prefix bst: <http://bsfs.ai/schema/Tag#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:User rdfs:subClassOf bsfs:Node .
+
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:shared rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:boolean ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bst:principal rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # removed: bsfs:Collection
+ # removed: xsd:string
+ # removed: bse:comment (bsfs:Entity -> xsd:string)
+ # removed: bse:partOf (bsfs:Entity -> bsfs:Collection)
+ # removed: bse:author (bsfs:entity -> bsfs:User)
+ # removed: bst:usedIn (bsfs:Tag -> bsfs:Collection)
+ # removed: bsc:tag (bsfs:Collection -> bsfs:Tag)
+
+ ''')
+ # store migrated to the new schema
+ store.schema = curr
+ self.assertEqual(store.schema, curr)
+ # instances of old classes were removed
+ self.assertSetEqual(set(store._graph), {
+ # node instances
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.User)),
+ # filesize
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ # tags
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ # shared
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_shared.uri), rdflib.Literal('true', datatype=rdflib.XSD.boolean)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_shared.uri), rdflib.Literal('true', datatype=rdflib.XSD.boolean)),
+ })
+
+ # can only assign schema instances
+ self.assertRaises(TypeError, setattr, store, 'schema', None)
+ self.assertRaises(TypeError, setattr, store, 'schema', 1234)
+ self.assertRaises(TypeError, setattr, store, 'schema', 'foo')
+ class Foo(): pass
+ self.assertRaises(TypeError, setattr, store, 'schema', Foo())
+
+ # cannot migrate to incompatible schema
+ invalid = _schema.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 .
+ bsfs:Tag rdfs:subClassOf bsfs:Entity . # inconsistent with previous tag definition
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ self.assertRaises(errors.ConsistencyError, setattr, store, 'schema', invalid)
+ invalid = _schema.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 .
+ bsfs:User rdfs:subClassOf bsfs:Node .
+
+ # inconsistent predicate
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:User;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ self.assertRaises(errors.ConsistencyError, setattr, store, 'schema', invalid)
+
+
+ def test_transaction(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ p_tag = store.schema.predicate(ns.bse.tag)
+ p_filesize = store.schema.predicate(ns.bse.filesize)
+ # prepare node types
+ ent_type = store.schema.node(ns.bsfs.Entity)
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ ent_ids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ tag_ids = {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')}
+ # target instances
+ instances = {
+ # node instances
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ # links
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ }
+
+ # add some data
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.set(ent_type, ent_ids, p_tag, tag_ids)
+ store.set(ent_type, ent_ids, p_filesize, {1234})
+ # current transaction is visible
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+ # rollback undoes previous changes
+ store.rollback()
+ self.assertSetEqual(set(store._graph), set())
+
+ # add some data once more
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.set(ent_type, ent_ids, p_tag, tag_ids)
+ store.set(ent_type, ent_ids, p_filesize, {1234})
+ # current transaction is visible
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+ # commit saves changes
+ store.commit()
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+ # add additional data
+ store.create(ent_type, {URI('http://example.com/me/entity#hello')})
+ store.set(ent_type, {URI('http://example.com/me/entity#hello')}, p_tag, tag_ids)
+ store.set(ent_type, ent_ids, p_filesize, {4321})
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#hello'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#hello'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#hello'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(4321, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(4321, datatype=rdflib.XSD.integer)),
+ })
+
+ # rollback undoes only changes since last commit
+ store.rollback()
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+
+ def test_exists(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ # prepare node types
+ ent_type = store.schema.node(ns.bsfs.Entity)
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ # create node instances
+ ent_ids = {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321'),
+ }
+ tag_ids = {
+ URI('http://example.com/me/tag#1234'),
+ URI('http://example.com/me/tag#4321'),
+ }
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+
+ # exists returns all existing nodes of the correct type
+ self.assertSetEqual(ent_ids, set(store.exists(ent_type, ent_ids)))
+ self.assertSetEqual(tag_ids, set(store.exists(tag_type, tag_ids)))
+ # exists returns only nodes that match the type
+ self.assertSetEqual(set(), set(store.exists(ent_type, tag_ids)))
+ self.assertSetEqual({URI('http://example.com/me/entity#1234')}, set(store.exists(ent_type, {
+ URI('http://example.com/me/tag#1234'),
+ URI('http://example.com/me/entity#1234'),
+ })))
+ # exists returns only nodes that exist
+ self.assertSetEqual(set(), set(store.exists(ent_type, {
+ URI('http://example.com/me/entity#foo'),
+ URI('http://example.com/me/entity#bar'),
+ })))
+ self.assertSetEqual({URI('http://example.com/me/entity#1234')}, set(store.exists(ent_type, {
+ URI('http://example.com/me/entity#foo'),
+ URI('http://example.com/me/entity#1234'),
+ })))
+
+
+ def test_create(self):
+ # setup
+ store = SparqlStore.Open()
+ 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), {
+ URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+
+ # can create some nodes
+ ent_type = store.schema.node(ns.bsfs.Entity)
+ store.create(ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ self.assertSetEqual(set(store._graph), {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ })
+
+ # existing nodes are skipped
+ store.create(ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#5678')})
+ self.assertSetEqual(set(store._graph), {
+ # previous triples
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ # new triples
+ (rdflib.URIRef('http://example.com/me/entity#5678'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ })
+
+ # can create nodes of a different type
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ store.create(tag_type, {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ self.assertSetEqual(set(store._graph), {
+ # previous triples
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#5678'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ # new triples
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ })
+
+ # creation does not change types of existing nodes
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ store.create(tag_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ self.assertSetEqual(set(store._graph), {
+ # previous triples
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#5678'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ # new triples
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ })
+
+
+ def test_set(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ # prepare node types
+ ent_type = store.schema.node(ns.bsfs.Entity)
+ user_type = store.schema.node(ns.bsfs.User)
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ # prepare predicates
+ p_filesize = store.schema.predicate(ns.bse.filesize)
+ 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))
+ # create node instances
+ ent_ids = {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321'),
+ }
+ tag_ids = {
+ URI('http://example.com/me/tag#1234'),
+ URI('http://example.com/me/tag#4321'),
+ URI('http://example.com/me/tag#foo'),
+ URI('http://example.com/me/tag#bar'),
+ URI('http://example.com/me/tag#foobar'),
+ URI('http://example.com/me/tag#xyz'),
+ }
+ user_ids = {
+ URI('http://example.com/me/user#1234'),
+ URI('http://example.com/me/user#4321'),
+ }
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ 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),
+ ent_ids, p_comment, {'hello world'})
+
+ # invalid predicate is not permitted
+ self.assertRaises(errors.ConsistencyError, store.set, ent_type, ent_ids, p_invalid, {'http://example.com/me/tag#1234'})
+
+ # predicate must match node_type
+ self.assertRaises(errors.ConsistencyError, store.set, tag_type, tag_ids, p_filesize, {1234})
+
+ # empty value does not change the graph
+ plen = len(store._graph)
+ store.set(ent_type, ent_ids, p_filesize, [])
+ store.set(ent_type, ent_ids, p_comment, [])
+ store.set(ent_type, ent_ids, p_author, [])
+ store.set(ent_type, ent_ids, p_tag, [])
+ self.assertEqual(plen, len(store._graph))
+
+ # cannot set multiple values on unique predicates
+ self.assertRaises(ValueError, store.set, ent_type, ent_ids, p_filesize, {1234, 4321})
+ self.assertRaises(ValueError, store.set, ent_type, ent_ids, p_author, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')})
+
+ # value nodes must exist
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_author, {URI('http://example.com/me/user#invalid')})
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_tag, {URI('http://example.com/me/tag#invalid')})
+
+ # value node types must be consistent with the predicate
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_author, {URI('http://example.com/me/entity#1234')})
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_tag, {URI('http://example.com/me/entity#1234')})
+
+ # all value nodes must exist and be consistent
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_tag, {
+ URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#invalid'), URI('http://example.com/me/entity#1234')})
+
+
+ # set unique literal
+ store.set(ent_type, ent_ids, p_filesize, {1234})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ # re-assigning the same node changes nothing
+ store.set(ent_type, ent_ids, p_filesize, {1234})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ # cannot set multiple unique literals
+ self.assertRaises(ValueError, store.set, ent_type, ent_ids, p_filesize, {1234, 4321}) # same test as above
+ # unique literals are overwritten by set
+ store.set(ent_type, ent_ids, p_filesize, {4321})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('4321', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertNotIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('4321', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertNotIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+
+ # set non-unique literal
+ store.set(ent_type, ent_ids, p_comment, {'foobar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foobar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foobar', datatype=rdflib.XSD.string)),
+ }))
+ # re-assigning the same node changes nothing
+ store.set(ent_type, ent_ids, p_comment, {'foobar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foobar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foobar', datatype=rdflib.XSD.string)),
+ }))
+ # can set multiple non-unique literals at once
+ store.set(ent_type, ent_ids, p_comment, {'foo', 'bar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ }))
+ # non-unique literals are appended by set
+ store.set(ent_type, ent_ids, p_comment, {'hello world'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('hello world', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('hello world', datatype=rdflib.XSD.string)),
+ }))
+
+ # set unique node
+ store.set(ent_type, ent_ids, p_author, {URI('http://example.com/me/user#1234')})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ # re-assigning the same node changes nothing
+ store.set(ent_type, ent_ids, p_author, {URI('http://example.com/me/user#1234')})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ # cannot set multiple unique nodes
+ self.assertRaises(ValueError, store.set, ent_type, ent_ids, p_author, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')})
+ # unique nodes are overwritten by set
+ store.set(ent_type, ent_ids, p_author, {URI('http://example.com/me/user#4321')})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#4321')),
+ set(store._graph))
+ self.assertNotIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#4321')),
+ set(store._graph))
+ self.assertNotIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+
+ # set non-unique node
+ store.set(ent_type, ent_ids, p_tag, {'http://example.com/me/tag#foobar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foobar')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foobar')),
+ }))
+ # re-assigning the same node changes nothing
+ store.set(ent_type, ent_ids, p_tag, {'http://example.com/me/tag#foobar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foobar')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foobar')),
+ }))
+ # can set multiple non-unique literals at once
+ store.set(ent_type, ent_ids, p_tag, {'http://example.com/me/tag#1234', 'http://example.com/me/tag#4321'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ }))
+ # non-unique nodes are appended by set
+ store.set(ent_type, ent_ids, p_tag, {'http://example.com/me/tag#foo', 'http://example.com/me/tag#bar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foo')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#bar')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foo')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#bar')),
+ }))
+
+ # nothing happens when no guids are given
+ plen = len(store._graph)
+ store.set(ent_type, set(), p_comment, {'xyz'})
+ store.set(ent_type, set(), p_tag, {URI('http://example.com/me/tag#xyz')})
+ self.assertEqual(plen, len(store._graph))
+
+ # guids must be instances of node_type
+ self.assertRaises(errors.InstanceError, store.set, ent_type, tag_ids, p_comment, {'xyz'})
+ # inexistent guids
+ self.assertRaises(errors.InstanceError, store.set, ent_type, {URI('http://example.com/me/entity#foobar')}, p_comment, {'xyz'})
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/utils/__init__.py b/test/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/utils/__init__.py
diff --git a/test/utils/test_commons.py b/test/utils/test_commons.py
new file mode 100644
index 0000000..ce73788
--- /dev/null
+++ b/test/utils/test_commons.py
@@ -0,0 +1,31 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import unittest
+
+# objects to test
+from bsfs.utils.commons import typename
+
+
+## code ##
+
+class TestCommons(unittest.TestCase):
+ def test_typename(self):
+ class Foo(): pass
+ self.assertEqual(typename(Foo()), 'Foo')
+ self.assertEqual(typename('hello'), 'str')
+ self.assertEqual(typename(123), 'int')
+ self.assertEqual(typename(None), 'NoneType')
+
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/utils/test_uri.py b/test/utils/test_uri.py
new file mode 100644
index 0000000..770e65a
--- /dev/null
+++ b/test/utils/test_uri.py
@@ -0,0 +1,189 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import operator
+import unittest
+
+# objects to test
+from bsfs.utils.uri import URI
+
+
+## code ##
+
+class TestURI(unittest.TestCase):
+
+ def test_new(self):
+ # cannot create an unparseable URI
+ self.assertRaises(ValueError, URI, 'http://')
+ # returns URI otherwise
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment'), URI)
+
+ def test_is_parseable(self):
+ # empty string is a parseable uri
+ self.assertTrue(URI.is_parseable(''))
+ # examples from the RFC are parseable
+ self.assertTrue(URI.is_parseable('foo://example.com:8042/over/there?name=ferret#nose'))
+ self.assertTrue(URI.is_parseable('urn:example:animal:ferret:nose'))
+ self.assertTrue(URI.is_parseable('mailto:fred@xample.com'))
+ self.assertTrue(URI.is_parseable('www.w3.org/Addressing/'))
+ self.assertTrue(URI.is_parseable('ftp://cnn.example.com&store=breaking_news@10.0.0.1/top_story.htm'))
+ self.assertTrue(URI.is_parseable('ftp://ftp.is.co.za/rfc/rfc1808.txt'))
+ self.assertTrue(URI.is_parseable('http://www.ietf.org/rfc/rfc2396.txt'))
+ self.assertTrue(URI.is_parseable('ldap://[2001:db8::7]/c=GB?objectClass?one'))
+ self.assertTrue(URI.is_parseable('mailto:John.Doe@example.com'))
+ self.assertTrue(URI.is_parseable('news:comp.infosystems.www.servers.unix'))
+ self.assertTrue(URI.is_parseable('tel:+1-816-555-1212'))
+ self.assertTrue(URI.is_parseable('telnet://192.0.2.16:80/'))
+ self.assertTrue(URI.is_parseable('urn:oasis:names:specification:docbook:dtd:xml:4.1.2'))
+
+ # uri cannot end with a scheme delimiter
+ self.assertFalse(URI.is_parseable('http://'))
+ # port must be a number
+ self.assertFalse(URI.is_parseable('http://example.com:foo/'))
+ # the double slash (//) implies a authority
+ self.assertFalse(URI.is_parseable('http:///path0/path1?query#fragment'))
+
+ def test_compose(self):
+ self.assertEqual(URI.compose('path'), '/path')
+ self.assertEqual(URI.compose('/path'), '/path') # leading slash is not repeated
+ self.assertEqual(URI.compose('path', scheme='scheme'), 'scheme:/path')
+ self.assertEqual(URI.compose('path', authority='authority'), '//authority/path')
+ self.assertEqual(URI.compose('path', host='host'), '//host/path')
+ self.assertEqual(URI.compose('path', user='user'), '/path') # user w/o host is ignored
+ self.assertEqual(URI.compose('path', host='host', user='user'), '//user@host/path')
+ self.assertEqual(URI.compose('path', port='port'), '/path') # port w/o host is ignored
+ self.assertEqual(URI.compose('path', host='host', port=1234), '//host:1234/path')
+ self.assertEqual(URI.compose('path', host='host', port='1234'), '//host:1234/path')
+ self.assertRaises(ValueError, URI.compose, 'path', host='host', port='foo') # port must be a number
+ self.assertEqual(URI.compose('path', host='host', user='foo', port='1234'), '//foo@host:1234/path')
+ self.assertEqual(URI.compose('path', query='query'), '/path?query')
+ self.assertEqual(URI.compose('path', fragment='fragment'), '/path#fragment')
+ self.assertEqual(URI.compose('path', 'scheme', 'authority', 'user', 'host', 1234, 'query', 'fragment'),
+ 'scheme://user@host:1234/path?query#fragment')
+
+ def test_get(self):
+ # get returns the respective component
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('scheme'), 'http')
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('authority'), 'user@www.example.com:1234')
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('userinfo'), 'user')
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('host'), 'www.example.com')
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('port'), 1234)
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('path'), '/path0/path1')
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('query'), 'query')
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('fragment'), 'fragment')
+ # get returns a default value if the component is missing
+ class Foo(): pass
+ foo = Foo()
+ self.assertEqual(URI('//user@www.example.com:1234/path0/path1?query#fragment').get('scheme', foo), foo)
+ self.assertEqual(URI('/path0/path1?query#fragment').get('authority', foo), foo)
+ self.assertEqual(URI('http://www.example.com:1234/path0/path1?query#fragment').get('userinfo', foo), foo)
+ self.assertEqual(URI('/path0/path1?query#fragment').get('host', foo), foo)
+ self.assertEqual(URI('http://user@www.example.com/path0/path1?query#fragment').get('port', foo), foo)
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1#fragment').get('query', foo), foo)
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query').get('fragment', foo), foo)
+ # can only get components
+ self.assertRaises(ValueError, URI('').get, 'foobar')
+
+ def test_scheme(self):
+ # full URI
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').scheme, 'http')
+ self.assertEqual(URI('ftp://user@www.example.com:1234/path0/path1?query#fragment').scheme, 'ftp')
+ self.assertEqual(URI('myown://user@www.example.com:1234/path0/path1?query#fragment').scheme, 'myown')
+ # empty scheme
+ self.assertRaises(ValueError, getattr, URI('www.example.com/path0/path1?query#fragment'), 'scheme')
+ # empty URI
+ self.assertRaises(ValueError, getattr, URI(''), 'scheme')
+
+ def test_authority(self):
+ # full URI
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').authority, 'user@www.example.com:1234')
+ # empty authority
+ self.assertRaises(ValueError, getattr, URI('http/path0/path1?query#fragment'), 'authority')
+ # empty URI
+ self.assertRaises(ValueError, getattr, URI(''), 'authority')
+
+ def test_userinfo(self):
+ # full URI
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').scheme, 'http')
+ # empty authority
+ self.assertRaises(ValueError, getattr, URI('http/path0/path1?query#fragment'), 'userinfo')
+ # empty userinfo
+ self.assertRaises(ValueError, getattr, URI('http://www.example.com:1234/path0/path1?query#fragment'), 'userinfo')
+ # empty URI
+ self.assertRaises(ValueError, getattr, URI(''), 'userinfo')
+
+ def test_host(self):
+ # full URI
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').host, 'www.example.com')
+ # IPv4 host
+ self.assertEqual(URI('http://user@10.0.0.1:1234/path0/path1?query#fragment').host, '10.0.0.1')
+ # IPv6 host
+ self.assertEqual(URI('http://user@[::64]:1234/path0/path1?query#fragment').host, '[::64]')
+ # empty authority
+ self.assertRaises(ValueError, getattr, URI('http/path0/path1?query#fragment'), 'host')
+ # empty URI
+ self.assertRaises(ValueError, getattr, URI(''), 'host')
+
+ def test_port(self):
+ # full URI
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').port, 1234)
+ # empty authority
+ self.assertRaises(ValueError, getattr, URI('http/path0/path1?query#fragment'), 'port')
+ # empty port
+ self.assertRaises(ValueError, getattr, URI('http://user@www.example.com/path0/path1?query#fragment'), 'port')
+ # empty URI
+ self.assertRaises(ValueError, getattr, URI(''), 'port')
+
+ def test_path(self):
+ # full URI
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').path, '/path0/path1')
+ # empty path
+ self.assertEqual(URI('http://user@www.example.com:1234?query#fragment').path, '')
+ # empty URI
+ self.assertEqual(URI('').path, '')
+
+ def test_query(self):
+ # full URI
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').query, 'query')
+ # empty query
+ self.assertRaises(ValueError, getattr, URI('http://user@www.example.com:1234/path0/path1#fragment'), 'query')
+ # empty URI
+ self.assertRaises(ValueError, getattr, URI(''), 'query')
+
+ def test_fragment(self):
+ # full URI
+ self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').fragment, 'fragment')
+ # empty fragment
+ self.assertRaises(ValueError, getattr, URI('http://user@www.example.com:1234/path0/path1?query'), 'fragment')
+ # empty URI
+ self.assertRaises(ValueError, getattr, URI(''), 'fragment')
+
+ def test_overloaded(self):
+ # composition
+ self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment') + 'hello', URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment') * 2, URI)
+ self.assertIsInstance(2 * URI('http://user@www.example.com:1234/{}/path1?{}#fragment'), URI) # rmul
+ self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment').join(['hello', 'world']) , URI)
+ # stripping
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').strip(), URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').lstrip(), URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').rstrip(), URI)
+ # case fold
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').lower(), URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').upper(), URI)
+ # formatting
+ self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment').format('hello', 'world'), URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/%s/path1?%s#fragment') % ('hello', 'world'), URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').replace('path0', 'pathX'), URI)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/utils/test_uuid.py b/test/utils/test_uuid.py
new file mode 100644
index 0000000..49176d4
--- /dev/null
+++ b/test/utils/test_uuid.py
@@ -0,0 +1,92 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import os
+import re
+import unittest
+
+# objects to test
+from bsfs.utils.uuid import UUID, UCID
+
+
+## code ##
+
+class TestUUID(unittest.TestCase):
+ """Test the UUID generator.
+
+ The UUID is expected to generate random strings of 64 characters(0-9, A-F, case insensitive).
+ Due to the random nature of UUIDs, we cannot actually check if an uid is 'valid' besides
+ matching the expected format.
+
+ At best, we can check if the number of collisions (values generated repeatedly) is below some
+ threshold. One would expect the number of collisions to increase with the number of generated uids.
+ Hence, we only perform an empirical test, whereas the exact test parameters (NUM_SAMPLES,
+ COLLISIONS_THRESHOLD) are subject to the application requirements. Note that this simple test
+ cannot replace a thorough statistical analysis.
+
+ """
+
+ # expected uuid string format
+ _RX_FORMAT = re.compile('[0-9A-Fa-f]{64}')
+
+ # number of uuids to generate for collisions test
+ _NUM_SAMPLES = 100_000
+
+ # number of permitted collisions (less-than test; exclusive)
+ _COLLISIONS_THRESHOLD = 2 # zero or one collisions to pass the test
+
+ def _test_format(self, uid):
+ self.assertIsInstance(uid, str)
+ self.assertTrue(self._RX_FORMAT.fullmatch(uid) is not None)
+
+ def test_call(self):
+ gen = UUID()
+ # w/o content
+ self._test_format(gen())
+ # with content
+ self._test_format(gen('hello world'))
+
+ def test_iter(self):
+ for _, uid in zip(range(1_000), iter(UUID())):
+ self._test_format(uid)
+
+ def test_next(self):
+ gen = UUID()
+ for _ in range(1_000):
+ uid = next(gen)
+ self._test_format(uid)
+
+ def test_collisions(self):
+ # generated uuids are reasonably unique.
+ # Note that we cannot guarantee no collisions.
+ uids = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID())}
+ self.assertGreater(len(uids), self._NUM_SAMPLES - self._COLLISIONS_THRESHOLD)
+ # uuids are reasonably unique across instances
+ uidA = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID())}
+ uidB = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID())}
+ self.assertLess(len(uidA & uidB), self._COLLISIONS_THRESHOLD)
+ # uuids are reasonably unique despite identical seeds.
+ uidA = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID(seed=123))}
+ uidB = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID(seed=123))}
+ self.assertLess(len(uidA & uidB), self._COLLISIONS_THRESHOLD)
+
+
+class TestUCID(unittest.TestCase):
+ def setUp(self):
+ self._checksum = 'a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447' # sha256
+ self._path = os.path.join(os.path.dirname(__file__), 'testfile.t')
+
+ def test_from_path(self):
+ self.assertEqual(UCID.from_path(self._path), self._checksum)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/utils/testfile.t b/test/utils/testfile.t
new file mode 100644
index 0000000..3b18e51
--- /dev/null
+++ b/test/utils/testfile.t
@@ -0,0 +1 @@
+hello world