aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-03-05 19:25:29 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-03-05 19:25:29 +0100
commit48b6081d0092e9c5a1b0ad79bdde2e51649bf61a (patch)
tree634198c34aae3c0306ce30ac7452abd7b53a14e8 /test
parent91437ba89d35bf482f3d9671bb99ef2fc69f5985 (diff)
parente4845c627e97a6d125bf33d9e7a4a8d373d7fc4a (diff)
downloadbsfs-48b6081d0092e9c5a1b0ad79bdde2e51649bf61a.tar.gz
bsfs-48b6081d0092e9c5a1b0ad79bdde2e51649bf61a.tar.bz2
bsfs-48b6081d0092e9c5a1b0ad79bdde2e51649bf61a.zip
Merge branch 'develop'v0.23.03
Diffstat (limited to 'test')
-rw-r--r--test/apps/schema-1.nt4
-rw-r--r--test/apps/schema-2.nt7
-rw-r--r--test/apps/test_init.py5
-rw-r--r--test/apps/test_main.py37
-rw-r--r--test/apps/test_migrate.py15
-rw-r--r--test/front/test_bsfs.py8
-rw-r--r--test/front/test_builder.py8
-rw-r--r--test/graph/ac/test_base.py78
-rw-r--r--test/graph/ac/test_null.py76
-rw-r--r--test/graph/test_graph.py258
-rw-r--r--test/graph/test_nodes.py462
-rw-r--r--test/graph/test_resolve.py199
-rw-r--r--test/graph/test_result.py429
-rw-r--r--test/graph/test_walk.py170
-rw-r--r--test/namespace/test_namespace.py131
-rw-r--r--test/query/__init__.py0
-rw-r--r--test/query/ast_test/__init__.py0
-rw-r--r--test/query/ast_test/test_fetch.py234
-rw-r--r--test/query/ast_test/test_filter_.py614
-rw-r--r--test/query/test_matcher.py1177
-rw-r--r--test/query/test_validator.py505
-rw-r--r--test/schema/test_schema.py335
-rw-r--r--test/schema/test_serialize.py1048
-rw-r--r--test/schema/test_types.py250
-rw-r--r--test/triple_store/sparql/__init__.py0
-rw-r--r--test/triple_store/sparql/test_distance.py56
-rw-r--r--test/triple_store/sparql/test_parse_fetch.py257
-rw-r--r--test/triple_store/sparql/test_parse_filter.py777
-rw-r--r--test/triple_store/sparql/test_sparql.py (renamed from test/triple_store/test_sparql.py)292
-rw-r--r--test/triple_store/sparql/test_utils.py152
-rw-r--r--test/triple_store/test_base.py11
-rw-r--r--test/utils/test_commons.py22
-rw-r--r--test/utils/test_uri.py24
-rw-r--r--test/utils/test_uuid.py19
34 files changed, 6984 insertions, 676 deletions
diff --git a/test/apps/schema-1.nt b/test/apps/schema-1.nt
index e57146d..4daf0ad 100644
--- a/test/apps/schema-1.nt
+++ b/test/apps/schema-1.nt
@@ -3,8 +3,8 @@ 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#>
+prefix bsfs: <http://schema.bsfs.io/core/>
+prefix bse: <http://schema.bsfs.io/core/Node/Entity#>
# essential nodes
bsfs:Entity rdfs:subClassOf bsfs:Node .
diff --git a/test/apps/schema-2.nt b/test/apps/schema-2.nt
index 525ac99..4eb2467 100644
--- a/test/apps/schema-2.nt
+++ b/test/apps/schema-2.nt
@@ -3,14 +3,15 @@ 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#>
+prefix bsfs: <http://schema.bsfs.io/core/>
+prefix bse: <http://schema.bsfs.io/core/Node/Entity#>
# essential nodes
bsfs:Entity rdfs:subClassOf bsfs:Node .
# common definitions
-xsd:integer rdfs:subClassOf bsfs:Literal .
+bsfs:Number rdfs:subClassOf bsfs:Literal .
+xsd:integer rdfs:subClassOf bsfs:Number .
bse:filesize rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
diff --git a/test/apps/test_init.py b/test/apps/test_init.py
index bae6a68..59e10eb 100644
--- a/test/apps/test_init.py
+++ b/test/apps/test_init.py
@@ -1,9 +1,4 @@
-"""
-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
diff --git a/test/apps/test_main.py b/test/apps/test_main.py
new file mode 100644
index 0000000..d61372f
--- /dev/null
+++ b/test/apps/test_main.py
@@ -0,0 +1,37 @@
+
+# imports
+import contextlib
+import io
+import json
+import unittest
+
+# objects to test
+from bsfs.apps import main
+
+
+## code ##
+
+class TestMain(unittest.TestCase):
+ def test_main(self):
+ # must at least pass an app
+ with contextlib.redirect_stderr(io.StringIO()):
+ self.assertRaises(SystemExit, main, [])
+ # app takes over
+ with contextlib.redirect_stderr(io.StringIO()):
+ self.assertRaises(SystemExit, main, ['init'])
+ outbuf = io.StringIO()
+ with contextlib.redirect_stdout(outbuf):
+ main(['init', 'sparql'])
+ self.assertEqual(json.loads(outbuf.getvalue()), {
+ 'Graph': {
+ 'user': 'http://example.com/me',
+ 'backend': {
+ 'SparqlStore': {}}}})
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/apps/test_migrate.py b/test/apps/test_migrate.py
index 957509a..618cb37 100644
--- a/test/apps/test_migrate.py
+++ b/test/apps/test_migrate.py
@@ -1,9 +1,4 @@
-"""
-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
@@ -13,7 +8,7 @@ import unittest
import unittest.mock
# bsie imports
-from bsfs.schema import Schema
+from bsfs import schema
# objects to test
from bsfs.apps.migrate import main
@@ -33,21 +28,21 @@ class TestMigrate(unittest.TestCase):
# read schema from file
with open(schema_1) as ifile:
- target = Schema.from_string(ifile.read())
+ target = schema.from_string(ifile.read())
graph = main([config, schema_1])
self.assertTrue(target <= graph.schema)
# read schema from multiple files
with open(schema_1) as ifile:
- target = Schema.from_string(ifile.read())
+ target = schema.from_string(ifile.read())
with open(schema_2) as ifile:
- target = target + Schema.from_string(ifile.read())
+ target = target + schema.from_string(ifile.read())
graph = main([config, schema_1, schema_2])
self.assertTrue(target <= graph.schema)
# read schema from stdin
with open(schema_1, 'rt') as ifile:
- target = Schema.from_string(ifile.read())
+ target = schema.from_string(ifile.read())
with open(schema_1, 'rt') as ifile:
with unittest.mock.patch('sys.stdin', ifile):
graph = main([config])
diff --git a/test/front/test_bsfs.py b/test/front/test_bsfs.py
index 0d7f383..8905bf8 100644
--- a/test/front/test_bsfs.py
+++ b/test/front/test_bsfs.py
@@ -1,14 +1,10 @@
-"""
-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.graph.ac import NullAC
from bsfs.triple_store import SparqlStore
from bsfs.utils import errors, URI
@@ -25,7 +21,7 @@ class TestBSFS(unittest.TestCase):
graph = Open(config)
self.assertIsInstance(graph, Graph)
self.assertIsInstance(graph._backend, SparqlStore)
- self.assertEqual(graph._user, URI('http://example.com/me'))
+ self.assertEqual(graph._ac, NullAC(graph._backend, URI('http://example.com/me')))
# invalid config raises an error
self.assertRaises(errors.ConfigError, Open, {})
diff --git a/test/front/test_builder.py b/test/front/test_builder.py
index 08f2027..875fa8a 100644
--- a/test/front/test_builder.py
+++ b/test/front/test_builder.py
@@ -1,14 +1,10 @@
-"""
-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.graph.ac import NullAC
from bsfs.triple_store import SparqlStore
from bsfs.utils import errors, URI
@@ -40,7 +36,7 @@ class TestBuilder(unittest.TestCase):
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'))
+ self.assertEqual(graph._ac, NullAC(graph._backend, URI('http://example.com/me')))
# cannot create an invalid graph
self.assertRaises(errors.ConfigError, build_graph, {'MyGraph': {}})
# must pass a dict
diff --git a/test/graph/ac/test_base.py b/test/graph/ac/test_base.py
new file mode 100644
index 0000000..addecd4
--- /dev/null
+++ b/test/graph/ac/test_base.py
@@ -0,0 +1,78 @@
+
+# imports
+import unittest
+
+# bsie imports
+from bsfs import schema as bsc
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.triple_store import SparqlStore
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.graph.ac.base import AccessControlBase
+
+
+## code ##
+
+class StubAC(AccessControlBase):
+ def is_protected_predicate(self, pred):
+ pass
+ def create(self, node_type, guids):
+ pass
+ def link_from_node(self, node_type, guids):
+ pass
+ def link_to_node(self, node_type, guids):
+ pass
+ def write_literal(self, node_type, guids):
+ pass
+ def createable(self, node_type, guids):
+ pass
+ def filter_read(self, node_type, query):
+ pass
+ def fetch_read(self, node_type, query):
+ pass
+
+
+class TestAccessControlBase(unittest.TestCase):
+ def setUp(self):
+ self.backend = SparqlStore()
+ self.user = URI('http://www.example.com/me')
+
+ def test_essentials(self):
+ ac = StubAC(self.backend, self.user)
+ # equal construction means equal instance
+ self.assertEqual(StubAC(self.backend, self.user), StubAC(self.backend, self.user))
+ self.assertEqual(hash(StubAC(self.backend, self.user)), hash(StubAC(self.backend, self.user)))
+ self.assertEqual(ac, StubAC(self.backend, self.user))
+ self.assertEqual(hash(ac), hash(StubAC(self.backend, self.user)))
+ # equivalence respects type
+ class Foo(): pass
+ self.assertNotEqual(ac, 1234)
+ self.assertNotEqual(hash(ac), hash(1234))
+ self.assertNotEqual(ac, 'hello')
+ self.assertNotEqual(hash(ac), hash('hello'))
+ self.assertNotEqual(ac, Foo())
+ self.assertNotEqual(hash(ac), hash(Foo()))
+ # equivalence respects backend
+ self.assertNotEqual(ac, StubAC(SparqlStore(), self.user))
+ self.assertNotEqual(hash(ac), hash(StubAC(SparqlStore(), self.user)))
+ # equivalence respects user
+ self.assertNotEqual(ac, StubAC(self.backend, URI('http://www.example.com/you')))
+ self.assertNotEqual(hash(ac), hash(StubAC(self.backend, URI('http://www.example.com/you'))))
+ # string conversion
+ self.assertEqual(str(ac), f'StubAC({self.user})')
+ self.assertEqual(repr(ac), f'StubAC({self.user})')
+ # string conversion respects user
+ self.assertEqual(str(StubAC(self.backend, URI('http://www.example.com/you'))),
+ f'StubAC(http://www.example.com/you)')
+ self.assertEqual(repr(StubAC(self.backend, URI('http://www.example.com/you'))),
+ f'StubAC(http://www.example.com/you)')
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/graph/ac/test_null.py b/test/graph/ac/test_null.py
index f39c9be..142bc23 100644
--- a/test/graph/ac/test_null.py
+++ b/test/graph/ac/test_null.py
@@ -1,15 +1,11 @@
-"""
-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 import schema as bsc
from bsfs.namespace import ns
+from bsfs.query import ast
from bsfs.triple_store import SparqlStore
from bsfs.utils import URI
@@ -19,24 +15,28 @@ from bsfs.graph.ac.null import NullAC
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestNullAC(unittest.TestCase):
def setUp(self):
self.backend = SparqlStore()
- self.backend.schema = _schema.Schema.from_string('''
+ self.backend.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bsm: <http://bsfs.ai/schema/Meta#>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsn: <https://schema.bsfs.io/core/Node#>
+ prefix bse: <https://schema.bsfs.io/core/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 .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
# predicates mandated by Nodes
- bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
rdfs:range xsd:integer ;
bsfs:unique "true"^^xsd:boolean .
@@ -62,10 +62,40 @@ class TestNullAC(unittest.TestCase):
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.p_created = self.backend.schema.predicate(ns.bsn.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_essentials(self):
+ ac = NullAC(self.backend, self.user)
+ # equal construction means equal instance
+ self.assertEqual(NullAC(self.backend, self.user), NullAC(self.backend, self.user))
+ self.assertEqual(hash(NullAC(self.backend, self.user)), hash(NullAC(self.backend, self.user)))
+ self.assertEqual(ac, NullAC(self.backend, self.user))
+ self.assertEqual(hash(ac), hash(NullAC(self.backend, self.user)))
+ # equivalence respects type
+ class Foo(): pass
+ self.assertNotEqual(ac, 1234)
+ self.assertNotEqual(hash(ac), hash(1234))
+ self.assertNotEqual(ac, 'hello')
+ self.assertNotEqual(hash(ac), hash('hello'))
+ self.assertNotEqual(ac, Foo())
+ self.assertNotEqual(hash(ac), hash(Foo()))
+ # equivalence respects backend
+ self.assertNotEqual(ac, NullAC(SparqlStore(), self.user))
+ self.assertNotEqual(hash(ac), hash(NullAC(SparqlStore(), self.user)))
+ # equivalence respects user
+ self.assertNotEqual(ac, NullAC(self.backend, URI('http://www.example.com/you')))
+ self.assertNotEqual(hash(ac), hash(NullAC(self.backend, URI('http://www.example.com/you'))))
+ # string conversion
+ self.assertEqual(str(ac), f'NullAC({self.user})')
+ self.assertEqual(repr(ac), f'NullAC({self.user})')
+ # string conversion respects user
+ self.assertEqual(str(NullAC(self.backend, URI('http://www.example.com/you'))),
+ f'NullAC(http://www.example.com/you)')
+ self.assertEqual(repr(NullAC(self.backend, URI('http://www.example.com/you'))),
+ f'NullAC(http://www.example.com/you)')
+
def test_is_protected_predicate(self):
ac = NullAC(self.backend, self.user)
self.assertTrue(ac.is_protected_predicate(self.p_created))
@@ -93,6 +123,26 @@ class TestNullAC(unittest.TestCase):
ac = NullAC(self.backend, self.user)
self.assertSetEqual(self.ent_ids, ac.createable(self.ent_type, self.ent_ids))
+ def test_filter_read(self):
+ query = ast.filter.Or(
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/tag#1234')),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/tag#4321')),
+ ast.filter.Any(ns.bse.author, ast.filter.Equals('Me, Myself, and I')))
+ ac = NullAC(self.backend, self.user)
+ # NullAC returns query
+ self.assertEqual(query, ac.filter_read(self.ent_type, query))
+ # query can be none
+ self.assertIsNone(ac.filter_read(self.ent_type, None))
+
+ def test_fetch_read(self):
+ query = ast.fetch.All(
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bse.label, 'tag_label')),
+ ast.fetch.Node(ns.bse.tag, 'tag_node'),
+ ast.fetch.Value(ns.bse.iso, 'iso'))
+ ac = NullAC(self.backend, self.user)
+ # NullAC returns query
+ self.assertEqual(query, ac.fetch_read(self.ent_type, query))
+
## main ##
diff --git a/test/graph/test_graph.py b/test/graph/test_graph.py
index 33cf6aa..167168d 100644
--- a/test/graph/test_graph.py
+++ b/test/graph/test_graph.py
@@ -1,18 +1,17 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
+from functools import reduce
+import operator
import unittest
# bsie imports
from bsfs import schema
+from bsfs.graph.ac import NullAC
+from bsfs.graph.nodes import Nodes
from bsfs.namespace import ns
+from bsfs.query import ast
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
@@ -20,83 +19,95 @@ from bsfs.graph.graph import Graph
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestGraph(unittest.TestCase):
def setUp(self):
- self.user = URI('http://example.com/me')
self.backend = SparqlStore.Open()
- self.backend.schema = schema.Schema.from_string('''
+ self.backend.schema = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
''')
+ self.user = URI('http://example.com/me')
+ self.ac = NullAC(self.backend, self.user)
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)')
+ self.assertEqual(str(Graph(self.backend, self.ac)),
+ 'Graph(SparqlStore(uri=None))')
+ self.assertEqual(repr(Graph(self.backend, self.ac)),
+ 'Graph(SparqlStore(uri=None), NullAC(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)')
+ self.assertEqual(str(Graph(Foo.Open(), self.ac)),
+ 'Graph(Foo(uri=None))')
+ self.assertEqual(repr(Graph(Foo.Open(), self.ac)),
+ 'Graph(Foo(uri=None), NullAC(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)')
+ self.assertEqual(str(Graph(self.backend, NullAC(self.backend, URI('http://example.com/you')))),
+ 'Graph(SparqlStore(uri=None))')
+ self.assertEqual(repr(Graph(self.backend, NullAC(self.backend, URI('http://example.com/you')))),
+ 'Graph(SparqlStore(uri=None), NullAC(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)')
+ self.assertEqual(str(Bar(self.backend, self.ac)),
+ 'Bar(SparqlStore(uri=None))')
+ self.assertEqual(repr(Bar(self.backend, self.ac)),
+ 'Bar(SparqlStore(uri=None), NullAC(http://example.com/me))')
def test_equality(self):
- graph = Graph(self.backend, self.user)
+ graph = Graph(self.backend, self.ac)
# 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)))
+ self.assertEqual(graph, Graph(self.backend, self.ac))
+ self.assertEqual(hash(graph), hash(Graph(self.backend, self.ac)))
# equality respects backend
- self.assertNotEqual(graph, Graph(SparqlStore.Open(), self.user))
- self.assertNotEqual(hash(graph), hash(Graph(SparqlStore.Open(), self.user)))
+ self.assertNotEqual(graph, Graph(SparqlStore.Open(), self.ac))
+ self.assertNotEqual(hash(graph), hash(Graph(SparqlStore.Open(), self.ac)))
# 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)
+ graph = Graph(self.backend, self.ac)
# 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)
+ graph = Graph(self.backend, self.ac)
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}))
+ Nodes(self.backend, self.ac, 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)
+ graph = Graph(self.backend, self.ac)
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))
+ Nodes(self.backend, self.ac, 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_empty(self):
+ graph = Graph(self.backend, self.ac)
+ # returns a Nodes instance
+ self.assertEqual(
+ graph.empty(ns.bsfs.Entity),
+ Nodes(self.backend, self.ac, graph.schema.node(ns.bsfs.Entity), set()))
+ # node_type must be in the schema
+ self.assertRaises(KeyError, graph.empty, ns.bsfs.Invalid)
+
def test_migrate(self):
# setup
- graph = Graph(self.backend, self.user)
+ graph = Graph(self.backend, self.ac)
# argument must be a schema
class Foo(): pass
@@ -117,14 +128,16 @@ class TestGraph(unittest.TestCase):
schema.Node(ns.bsfs.Node, None)))}), append=False)
# can migrate to compatible schema
- target_1 = schema.Schema.from_string('''
+ target_1 = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
bse:filename rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -141,27 +154,31 @@ class TestGraph(unittest.TestCase):
# new schema is applied
self.assertLess(target_1, graph.schema)
# graph appends its predicates
- self.assertEqual(graph.schema, target_1 + schema.Schema.from_string('''
+ self.assertEqual(graph.schema, target_1 + schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bsm: <http://bsfs.ai/schema/Meta#>
- xsd:integer rdfs:subClassOf bsfs:Literal .
- bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bsn: <https://schema.bsfs.io/core/Node#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:float rdfs:subClassOf bsl:Number .
+ bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
- rdfs:range xsd:integer ;
+ rdfs:range xsd:float ;
bsfs:unique "true"^^xsd:boolean .
'''))
# can overwrite the current schema
- target_2 = schema.Schema.from_string('''
+ target_2 = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <http://schema.bsfs.io/core/>
+ prefix bse: <http://schema.bsfs.io/core/Node/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
bse:filename rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -180,18 +197,147 @@ class TestGraph(unittest.TestCase):
# new schema is applied
self.assertLess(target_2, graph.schema)
# graph appends its predicates
- self.assertEqual(graph.schema, target_2 + schema.Schema.from_string('''
+ self.assertEqual(graph.schema, target_2 + schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bsm: <http://bsfs.ai/schema/Meta#>
- xsd:integer rdfs:subClassOf bsfs:Literal .
- bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bsn: <https://schema.bsfs.io/core/Node#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:float rdfs:subClassOf bsl:Number .
+ bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
- rdfs:range xsd:integer ;
+ rdfs:range xsd:float ;
bsfs:unique "true"^^xsd:boolean .
'''))
+ def test_get(self):
+ # setup
+ graph = Graph(self.backend, self.ac)
+ graph.migrate(schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ 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 "false"^^xsd:boolean .
+
+ '''))
+ # add some instances
+ ents = graph.nodes(ns.bsfs.Entity, {URI('http://example.com/entity#1234'), URI('http://example.com/entity#4321')})
+ tags = graph.nodes(ns.bsfs.Tag, {URI('http://example.com/tag#1234'), URI('http://example.com/tag#4321')})
+ # add some node links
+ ents.set(ns.bse.tag, tags)
+ # add some literals
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'hello world')
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'foo')
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'foobar')
+ graph.node(ns.bsfs.Tag, URI('http://example.com/tag#1234')).set(ns.bse.comment, 'foo')
+ graph.node(ns.bsfs.Tag, URI('http://example.com/tag#4321')).set(ns.bse.comment, 'bar')
+
+ # invalid query raises exception
+ self.assertRaises(errors.ConsistencyError, graph.get, ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Equals('hello world')))
+
+ # get returns nodes
+ self.assertEqual(graph.get(ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Is(tags))), ents)
+ self.assertEqual(graph.get(ns.bsfs.Entity, ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('foo'))),
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')))
+ self.assertEqual(graph.get(ns.bsfs.Node, ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('foo'))),
+ graph.nodes(ns.bsfs.Node, {URI('http://example.com/entity#1234'), URI('http://example.com/tag#1234')}))
+ self.assertEqual(graph.get(ns.bsfs.Entity, ast.filter.Or(
+ ast.filter.Any(ns.bse.comment, ast.filter.EndsWith('bar')),
+ ast.filter.Any(ns.bse.tag, ast.filter.All(ns.bse.comment, ast.filter.Equals('bar'))))),
+ ents)
+
+ # query can be None
+ self.assertEqual(graph.get(ns.bsfs.Entity, None), ents)
+
+ def test_sorted(self):
+ # setup
+ graph = Graph(self.backend, self.ac)
+ graph.migrate(schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ 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 "false"^^xsd:boolean .
+
+ '''))
+ # add some instances
+ ents = [
+ # default is alphabetical order
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')),
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#4321')),
+ ]
+ tags = graph.nodes(ns.bsfs.Tag, {URI('http://example.com/tag#1234'), URI('http://example.com/tag#4321')})
+ # add some node links
+ reduce(operator.add, ents).set(ns.bse.tag, tags)
+ # add some literals
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'hello world')
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'foo')
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'foobar')
+ graph.node(ns.bsfs.Tag, URI('http://example.com/tag#1234')).set(ns.bse.comment, 'foo')
+ graph.node(ns.bsfs.Tag, URI('http://example.com/tag#4321')).set(ns.bse.comment, 'bar')
+
+ # invalid query raises exception
+ self.assertRaises(errors.ConsistencyError, list, graph.sorted(ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Equals('hello world'))))
+
+ # get returns nodes
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Is(tags)))), ents)
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Entity, ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('foo')))),
+ [graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234'))])
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Node, ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('foo')))), [
+ graph.node(ns.bsfs.Node, URI('http://example.com/entity#1234')),
+ graph.node(ns.bsfs.Node, URI('http://example.com/tag#1234')),
+ ])
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Entity, ast.filter.Or(
+ ast.filter.Any(ns.bse.comment, ast.filter.EndsWith('bar')),
+ ast.filter.Any(ns.bse.tag, ast.filter.All(ns.bse.comment, ast.filter.Equals('bar')))))),
+ ents)
+
+ # query can be None
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Entity, None)), ents)
+
+
+ def test_all(self):
+ graph = Graph(self.backend, self.ac)
+ # resulting nodes can be empty
+ self.assertEqual(graph.all(ns.bsfs.Entity),
+ Nodes(self.backend, self.ac, graph.schema.node(ns.bsfs.Entity), set()))
+ # resulting nodes contains all nodes of the respective type
+ guids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ self.backend.create(graph.schema.node(ns.bsfs.Entity), guids)
+ self.assertEqual(graph.all(ns.bsfs.Entity),
+ Nodes(self.backend, self.ac, graph.schema.node(ns.bsfs.Entity), guids))
+ # node_type must be in the schema
+ self.assertRaises(KeyError, graph.all, ns.bsfs.Invalid)
+
+
## main ##
diff --git a/test/graph/test_nodes.py b/test/graph/test_nodes.py
index 43e7f6f..afe7522 100644
--- a/test/graph/test_nodes.py
+++ b/test/graph/test_nodes.py
@@ -1,16 +1,17 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
-# imports
-import rdflib
+# standard imports
+from functools import partial
+import operator
import unittest
+# external imports
+import rdflib
+
# bsie imports
-from bsfs import schema as _schema
-from bsfs.namespace import ns
+from bsfs import schema as bsc
+from bsfs.graph.ac import NullAC
+from bsfs.graph.walk import Walk
+from bsfs.namespace import Namespace, ns
from bsfs.triple_store.sparql import SparqlStore
from bsfs.utils import errors, URI
@@ -20,27 +21,32 @@ from bsfs.graph.nodes import Nodes
## code ##
+ns.bse = ns.bsfs.Entity()
+ns.bst = ns.bsfs.Tag()
+
class TestNodes(unittest.TestCase):
def setUp(self):
# initialize backend
self.backend = SparqlStore()
- self.backend.schema = _schema.Schema.from_string('''
+ self.backend.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- 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#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsn: <https://schema.bsfs.io/core/Node#>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/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 .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
# predicates mandated by Nodes
- bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
rdfs:range xsd:integer ;
bsfs:unique "true"^^xsd:boolean .
@@ -66,14 +72,40 @@ class TestNodes(unittest.TestCase):
rdfs:range bsfs:User ;
bsfs:unique "true"^^xsd:boolean .
+ bst:label rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range xsd:string ;
+ bsfs:unique "true"^^xsd:boolean .
+
bst:representative rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Tag ;
rdfs:range bsfs:Entity ;
bsfs:unique "true"^^xsd:boolean .
''')
+ self.schema_triples = {
+ # schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Entity), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.string), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)),
+ (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)),
+ (rdflib.URIRef(ns.bsn.t_created), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.comment), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.author), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bst.representative), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bst.label), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ }
# Nodes constructor args
self.user = URI('http://example.com/me')
+ self.ac = NullAC(self.backend, self.user)
# set args
self.tag_type = self.backend.schema.node(ns.bsfs.Tag)
self.ent_type = self.backend.schema.node(ns.bsfs.Entity)
@@ -81,8 +113,9 @@ class TestNodes(unittest.TestCase):
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.p_representative = self.backend.schema.predicate(ns.bst.representative)
+ self.p_label = self.backend.schema.predicate(ns.bst.label)
+ self.t_created = self.backend.schema.predicate(ns.bsn.t_created)
self.ent_ids = {
URI('http://example.com/me/entity#1234'),
URI('http://example.com/me/entity#4321'),
@@ -92,67 +125,71 @@ class TestNodes(unittest.TestCase):
URI('http://example.com/me/tag#4321'),
}
+ def test_construct(self):
+ self.assertIsInstance(Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me-and-you'}), Nodes)
+ self.assertRaises(ValueError, Nodes, self.backend, self.ac, self.ent_type, {'http://example.com/me and you'})
+
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})')
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')})
+ self.assertEqual(str(nodes), f"Nodes({self.ent_type}, {{'http://example.com/me/entity#1234'}})")
+ self.assertEqual(repr(nodes), f"Nodes({self.backend}, {self.ac}, {self.ent_type}, {{'http://example.com/me/entity#1234'}})")
# 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})')
+ nodes = Nodes(self.backend, self.ac, self.tag_type, {URI('http://example.com/me/tag#1234')})
+ self.assertEqual(str(nodes), f"Nodes({self.tag_type}, {{'http://example.com/me/tag#1234'}})")
+ self.assertEqual(repr(nodes), f"Nodes({self.backend}, {self.ac}, {self.tag_type}, {{'http://example.com/me/tag#1234'}})")
# str respects guids
- nodes = Nodes(self.backend, self.user, self.ent_type, {URI('http://example.com/me/entity#foo')})
+ nodes = Nodes(self.backend, self.ac, 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\'}})')
+ self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.ac}, {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})')
+ nodes = Nodes(backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')})
+ self.assertEqual(repr(nodes), f"Nodes({backend}, {self.ac}, {self.ent_type}, {{'http://example.com/me/entity#1234'}})")
# 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})')
+ nodes = Nodes(self.backend, NullAC(self.backend, URI('http://example.com/you')), self.ent_type, {URI('http://example.com/me/entity#1234')})
+ self.assertEqual(repr(nodes), f"Nodes({self.backend}, NullAC(http://example.com/you), {self.ent_type}, {{'http://example.com/me/entity#1234'}})")
def test_equality(self):
- nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ nodes = Nodes(self.backend, self.ac, 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)))
+ self.assertEqual(nodes, Nodes(self.backend, self.ac, self.ent_type, self.ent_ids))
+ self.assertEqual(Nodes(self.backend, self.ac, self.ent_type, self.ent_ids), nodes)
+ self.assertEqual(hash(nodes), hash(Nodes(self.backend, self.ac, 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)))
+ self.assertNotEqual(nodes, Nodes(backend, self.ac, self.ent_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(backend, self.ac, 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)))
+ self.assertNotEqual(nodes, Nodes(self.backend, NullAC(self.backend, URI('http://example.com/you')), self.ent_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, NullAC(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)))
+ self.assertNotEqual(nodes, Nodes(self.backend, self.ac, self.tag_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.ac, 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)))
+ self.assertNotEqual(nodes, Nodes(self.backend, self.ac, self.ent_type, self.tag_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.ac, 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.backend, self.ac, 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)
+ self.backend, self.ac, 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.backend, self.ac, 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))
+ self.backend, self.ac, self.tag_type, self.tag_ids).guids))
def test__ensure_nodes(self):
- nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)
# missing nodes are created
self.assertSetEqual(self.ent_ids, nodes._ensure_nodes(self.ent_type, self.ent_ids))
@@ -160,10 +197,10 @@ class TestNodes(unittest.TestCase):
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), {
+ self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# 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')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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)),
@@ -171,10 +208,10 @@ class TestNodes(unittest.TestCase):
# existing nodes remain unchanged
self.assertSetEqual(self.ent_ids, nodes._ensure_nodes(self.ent_type, self.ent_ids))
- self.assertSetEqual(set(self.backend._graph), {
+ self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# 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')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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)),
@@ -186,23 +223,23 @@ class TestNodes(unittest.TestCase):
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), {
+ self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# 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.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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())
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ self.assertSetEqual(set(self.backend._graph), self.schema_triples | set())
set_ = nodes._Nodes__set
# node_type must match predicate's domain
@@ -217,10 +254,10 @@ class TestNodes(unittest.TestCase):
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), {
+ self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# 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')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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)),
@@ -230,23 +267,23 @@ class TestNodes(unittest.TestCase):
})
# 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')})
+ tags = Nodes(self.backend, self.ac, 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), {
+ self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# 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.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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)),
@@ -262,29 +299,29 @@ class TestNodes(unittest.TestCase):
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))
+ Nodes(self.backend, self.ac, 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)
+ self.assertSetEqual(set(self.backend._graph), self.schema_triples | set())
+ nodes = Nodes(self.backend, self.ac, 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')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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.assertEqual(nodes, nodes.set(self.p_tag.uri, Nodes(self.backend, self.ac, 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')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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')),
@@ -307,24 +344,28 @@ class TestNodes(unittest.TestCase):
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')}))
+ Nodes(self.backend, self.ac, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')}))
self.assertSetEqual(curr, set(self.backend._graph))
+ # can set on empty nodes
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {})
+ self.assertEqual(nodes, nodes.set(self.p_filesize.uri, 1234))
+
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)
+ self.assertSetEqual(set(self.backend._graph), self.schema_triples | set())
+ nodes = Nodes(self.backend, self.ac, 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),
+ self.p_tag.uri: Nodes(self.backend, self.ac, 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')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/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)),
@@ -349,9 +390,254 @@ class TestNodes(unittest.TestCase):
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.p_author.uri, Nodes(self.backend, self.ac, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')}))))
self.assertSetEqual(curr, set(self.backend._graph))
+ # can set on empty nodes
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {})
+ self.assertEqual(nodes, nodes.set_from_iterable([(self.p_filesize.uri, 1234)]))
+
+
+ def test_get(self):
+ # setup: add some instances
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}) \
+ .set(ns.bse.comment, 'hello world') \
+ .set(ns.bse.filesize, 1234) \
+ .set(ns.bse.tag, Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'}))
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}) \
+ .set(ns.bse.filesize, 4321) \
+ .set(ns.bse.tag, Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'}))
+ Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'}) \
+ .set(ns.bst.label, 'tag_label_1234')
+ Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'}) \
+ .set(ns.bst.label, 'tag_label_4321')
+ # setup: get nodes instance
+ nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)
+
+ # must pass at least one path
+ self.assertRaises(AttributeError, nodes.get)
+ # view must be list or dict
+ self.assertRaises(ValueError, nodes.get, ns.bse.filesize, view='hello')
+ self.assertRaises(ValueError, nodes.get, ns.bse.filesize, view=1234)
+ self.assertRaises(ValueError, nodes.get, ns.bse.filesize, view=tuple)
+ # can pass path as URI
+ self.assertDictEqual(nodes.get(ns.bse.filesize), {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): 1234,
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): 4321,
+ })
+ # can pass path as sequence of URI
+ self.assertDictEqual(nodes.get((ns.bse.tag, ns.bst.label)), {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {'tag_label_1234'},
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): {'tag_label_4321'},
+ })
+ # get returns the same path that was passed
+ self.assertCountEqual(list(nodes.get((ns.bse.tag, ns.bst.label), path=True, view=list)), [
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), (ns.bse.tag, ns.bst.label), 'tag_label_1234'),
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), (ns.bse.tag, ns.bst.label), 'tag_label_4321'),
+ ])
+ self.assertCountEqual(list(nodes.get([ns.bse.tag, ns.bst.label], path=True, view=list)), [
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), [ns.bse.tag, ns.bst.label], 'tag_label_1234'),
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), [ns.bse.tag, ns.bst.label], 'tag_label_4321'),
+ ])
+ # paths must be URI or sequence thereof
+ self.assertRaises(TypeError, nodes.get, 1234)
+ self.assertRaises(TypeError, nodes.get, (ns.bse.tag, 1234))
+ self.assertRaises(TypeError, nodes.get, (1234, ns.bse.tag))
+ self.assertRaises(ValueError, nodes.get, 'hello world')
+ self.assertRaises(errors.ConsistencyError, nodes.get, 'hello_world')
+ self.assertRaises(errors.ConsistencyError, nodes.get, ns.bse.invalid)
+ self.assertRaises(errors.ConsistencyError, nodes.get, (ns.bse.tag, ns.bst.invalid))
+ # can pass multiple paths
+ self.assertDictEqual(nodes.get(ns.bse.filesize, (ns.bse.tag, ns.bst.label)), {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {
+ ns.bse.filesize: 1234,
+ (ns.bse.tag, ns.bst.label): {'tag_label_1234'},
+ },
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): {
+ ns.bse.filesize: 4321,
+ (ns.bse.tag, ns.bst.label): {'tag_label_4321'},
+ },
+ })
+ # get respects view
+ self.assertDictEqual(nodes.get(ns.bse.filesize, view=dict), {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): 1234,
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): 4321,
+ })
+ self.assertSetEqual(set(nodes.get(ns.bse.filesize, view=list)), {
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), 1234),
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), 4321),
+ })
+ # get returns Nodes instance when fetching a node
+ self.assertDictEqual(nodes.get(ns.bse.tag), {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}):
+ {Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'})},
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}):
+ {Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'})},
+ })
+ # get returns a value when fetching a value and omits missing values
+ self.assertDictEqual(nodes.get(ns.bse.comment), {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {'hello world'},
+ })
+
+ # results can be empty
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}) # has filesize, tag but no comment
+ # unique paths return the default value
+ self.assertIsNone(nodes.get(ns.bse.author))
+ self.assertEqual(nodes.get(ns.bse.author, default=1234), 1234)
+ # non-unique paths return an empty set
+ self.assertSetEqual(nodes.get(ns.bse.comment), set())
+
+ # nodes can have no guids
+ nodes = Nodes(self.backend, self.ac, self.ent_type, set())
+ # empty nodes does not excuse an invalid request
+ self.assertRaises(TypeError, nodes.get, 1234)
+ self.assertRaises(errors.ConsistencyError, nodes.get, ns.bse.invalid)
+ # list view always returns an empty list
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, path=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=True, path=True, value=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, path=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=False, path=False, value=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, path=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=True, path=True, value=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, path=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=False, path=False, value=False)), [])
+ # dict view returns an empty dict or an empty set
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, node=True), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, path=True), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, node=True, path=True, value=True, default=None), {})
+ self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False), set())
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, path=False), {})
+ self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False, path=False), set())
+ self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False, path=False, value=False, default=None), set())
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=True), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, path=True), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=True, path=True, value=True, default=None), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, path=False), {})
+ self.assertSetEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False, path=False), set())
+ self.assertSetEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False, path=False, value=False, default=None), set())
+
+
+ def test_getattr(self):
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'})
+ # can get walks to values
+ self.assertEqual(nodes.filesize, Walk(nodes, (self.p_filesize, )))
+ # can get walks to nodes
+ self.assertEqual(nodes.tag, Walk(nodes, (self.p_tag, )))
+ # can do multiple hops
+ self.assertEqual(nodes.tag.label, Walk(nodes, (self.p_tag, self.p_label)))
+ # invalid step raises an error
+ self.assertRaises(ValueError, getattr, nodes, 'foobar')
+
+ def test_schema(self):
+ self.assertEqual(Nodes(self.backend, self.ac, self.ent_type,
+ {URI('http://example.com/me/entity#1234')}).schema, self.backend.schema)
+
+ def test_operators(self): # __add__, __or__, __sub__, __and__
+ gen = partial(Nodes, self.backend, self.ac, self.ent_type)
+ nodes = gen({URI('http://example.com/me/entity#1234')})
+ # add/or concatenates guids
+ self.assertEqual(
+ gen({URI('http://example.com/me/entity#1234')}) +
+ gen({URI('http://example.com/me/entity#4321')}),
+ # target
+ gen({
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321')}))
+ self.assertEqual(
+ gen({URI('http://example.com/me/entity#1234')}) |
+ gen({URI('http://example.com/me/entity#4321')}),
+ # target
+ gen({
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321')}))
+ # repeated guids are ignored
+ self.assertEqual(
+ gen({URI('http://example.com/me/entity#1234')}) +
+ gen({URI('http://example.com/me/entity#1234')}),
+ # target
+ gen({URI('http://example.com/me/entity#1234')}))
+ self.assertEqual(
+ gen({URI('http://example.com/me/entity#1234')}) |
+ gen({URI('http://example.com/me/entity#1234')}),
+ # target
+ gen({URI('http://example.com/me/entity#1234')}))
+
+ # sub substracts guids
+ self.assertEqual(
+ gen({URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321')}) -
+ gen({URI('http://example.com/me/entity#4321')}),
+ # target
+ gen({URI('http://example.com/me/entity#1234')}))
+ # missing guids are ignored
+ self.assertEqual(
+ gen({URI('http://example.com/me/entity#1234')}) -
+ gen({URI('http://example.com/me/entity#4321')}),
+ # target
+ gen({URI('http://example.com/me/entity#1234')}))
+
+ # and intersects guids
+ self.assertEqual(
+ gen({URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321')}) &
+ gen({URI('http://example.com/me/entity#4321'),
+ URI('http://example.com/me/entity#5678')}),
+ # target
+ gen({URI('http://example.com/me/entity#4321')}))
+
+ for op in (operator.add, operator.or_, operator.sub, operator.and_):
+ # type must match
+ self.assertRaises(TypeError, op, nodes, 1234)
+ self.assertRaises(TypeError, op, nodes, 'hello world')
+ # backend must match
+ self.assertRaises(ValueError, op, nodes,
+ Nodes(None, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')}))
+ # ac must match
+ self.assertRaises(ValueError, op, nodes,
+ Nodes(self.backend, NullAC(self.backend, ''),
+ self.ent_type, {URI('http://example.com/me/entity#1234')}))
+ # node type must match
+ self.assertRaises(ValueError, op, nodes,
+ Nodes(self.backend, self.ac, self.tag_type, {URI('http://example.com/me/entity#1234')}))
+
+ def test_len(self):
+ self.assertEqual(1, len(Nodes(self.backend, self.ac, self.ent_type, {
+ URI('http://example.com/me/entity#1234'),
+ })))
+ self.assertEqual(2, len(Nodes(self.backend, self.ac, self.ent_type, {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321'),
+ })))
+ self.assertEqual(4, len(Nodes(self.backend, self.ac, self.ent_type, {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321'),
+ URI('http://example.com/me/entity#5678'),
+ URI('http://example.com/me/entity#8765'),
+ })))
+
+ def test_iter(self): # __iter__
+ gen = partial(Nodes, self.backend, self.ac, self.ent_type)
+ self.assertSetEqual(set(Nodes(self.backend, self.ac, self.ent_type, {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321'),
+ URI('http://example.com/me/entity#5678'),
+ URI('http://example.com/me/entity#8765'),
+ })), {
+ gen({URI('http://example.com/me/entity#1234')}),
+ gen({URI('http://example.com/me/entity#4321')}),
+ gen({URI('http://example.com/me/entity#5678')}),
+ gen({URI('http://example.com/me/entity#8765')}),
+ })
+
## main ##
diff --git a/test/graph/test_resolve.py b/test/graph/test_resolve.py
new file mode 100644
index 0000000..e09b1cc
--- /dev/null
+++ b/test/graph/test_resolve.py
@@ -0,0 +1,199 @@
+
+# imports
+import unittest
+
+# bsie imports
+from bsfs import schema as bsc
+from bsfs.graph import Graph, nodes
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.triple_store import SparqlStore
+from bsfs.utils import URI, errors
+
+# objects to test
+from bsfs.graph.resolve import Filter
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+
+class TestFilter(unittest.TestCase):
+ """
+
+ NOTE: The Filter resolver is relatively simple as it only checks and changes
+ ast.filter.Is instances. Hence, we don't test all methods individually but
+ all of them with respect to ast.filter.Is elements.
+
+ """
+
+ def test_call(self): # tests resolve implicitly
+ schema = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array .
+ xsd:integer rdfs:subClassOf bsl:Number .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "5"^^xsd:integer .
+
+ bse:colors rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Colors .
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ 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 .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ backend = SparqlStore.Open()
+ backend.schema = schema
+ graph = Graph(backend, URI('http://example.com/me'))
+ ents = graph.nodes(ns.bsfs.Entity,
+ {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ tags = graph.nodes(ns.bsfs.Tag,
+ {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ invalid = nodes.Nodes(None, '', schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ {'http://example.com/you/invalid#1234', 'http://example.com/you/invalid#4321'})
+ resolver = Filter(schema)
+
+ # query can be None
+ self.assertIsNone(resolver(schema.node(ns.bsfs.Entity), None))
+
+ # immediate Is
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Is(ents)),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.filter.Is('http://example.com/me/entity#4321')
+ ))
+ # only resolves nodes instances, not URIs
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Is('http://example.com/me/entity#1234')),
+ ast.filter.Is('http://example.com/me/entity#1234'))
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Is(1234)),
+ ast.filter.Is(1234))
+
+ # within And (also checks _value)
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Is(ents),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('hello world')),
+ )),
+ ast.filter.And(
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.filter.Is('http://example.com/me/entity#4321')),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('hello world'))
+ ))
+ # within Or (checks _bounded)
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is(ents),
+ ast.filter.Any(ns.bse.filesize, ast.filter.LessThan(5)),
+ )),
+ ast.filter.Or(
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.filter.Is('http://example.com/me/entity#4321')),
+ ast.filter.Any(ns.bse.filesize, ast.filter.LessThan(5))
+ ))
+
+ # Any-branched Is
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is(tags))),
+ ast.filter.Any(ns.bse.tag, ast.filter.Or(
+ ast.filter.Is('http://example.com/me/tag#1234'),
+ ast.filter.Is('http://example.com/me/tag#4321')),
+ ))
+ # All-branched Is
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.All(ns.bse.tag, ast.filter.Is(tags))),
+ ast.filter.All(ns.bse.tag, ast.filter.Or(
+ ast.filter.Is('http://example.com/me/tag#1234'),
+ ast.filter.Is('http://example.com/me/tag#4321')),
+ ))
+ # Negated predicate
+ self.assertEqual(resolver(schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.tag, reverse=True), ast.filter.Is(ents))),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.tag, reverse=True), ast.filter.Or(
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.filter.Is('http://example.com/me/entity#4321')),
+ ))
+
+ # negated Is
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Not(ast.filter.Is(ents))),
+ ast.filter.Not(
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.filter.Is('http://example.com/me/entity#4321')),
+ ))
+
+ # for sake of completeness: Has
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.comment)),
+ ast.filter.Has(ns.bse.comment))
+ # for sake of completeness: Distance
+ self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.colors, ast.filter.Distance([1,2,3,4,5], 1))),
+ ast.filter.Any(ns.bse.colors, ast.filter.Distance([1,2,3,4,5], 1)))
+ # route errors
+ self.assertRaises(errors.BackendError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Predicate(ns.bse.comment))
+ self.assertRaises(errors.BackendError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ast.filter.PredicateExpression(), ast.filter.Equals('foo')))
+ self.assertRaises(errors.BackendError, resolver._one_of, ast.filter.OneOf(ast.filter.Predicate(ns.bsfs.Predicate)))
+ # for sake of coverage completeness: valid OneOf
+ self.assertIsNotNone(resolver._one_of(ast.filter.OneOf(ast.filter.Predicate(ns.bse.colors))))
+
+ # check schema consistency
+ self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Is(invalid))
+ # check immediate type compatibility
+ self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Is(ents))
+ self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Entity),
+ ast.filter.Is(tags))
+ # check type compatibility through branches
+ self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ns.bse.comment, ast.filter.Is(tags)))
+ self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ns.bse.invalid, ast.filter.Is(tags)))
+ self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.comment, ns.bse.tag), ast.filter.Is(tags)))
+ self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.comment, ns.bse.filesize), ast.filter.Is(tags)))
+ self.assertRaises(errors.ConsistencyError, resolver, schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.tag, reverse=True), ast.filter.Is(tags)))
+
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/graph/test_result.py b/test/graph/test_result.py
new file mode 100644
index 0000000..8960ef6
--- /dev/null
+++ b/test/graph/test_result.py
@@ -0,0 +1,429 @@
+
+# imports
+import unittest
+
+# bsie imports
+from bsfs import schema as bsc
+from bsfs.namespace import ns
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.graph.result import to_list_view, to_dict_view
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+
+class TestListView(unittest.TestCase):
+ def setUp(self):
+ self.triples_111 = [('ent#1234', ns.bse.iso, 123)]
+ self.triples_11U = [('ent#1234', ns.bse.tag, 'tag#1234'),
+ ('ent#1234', ns.bse.tag, 'tag#5678')]
+ self.triples_1M1 = [('ent#1234', ns.bse.iso, 123),
+ ('ent#1234', ns.bse.t_created, '2010-01-02')]
+ self.triples_1MU = [('ent#1234', ns.bse.iso, 123),
+ ('ent#1234', ns.bse.tag, 'tag#1234'),
+ ('ent#1234', ns.bse.tag, 'tag#5678')]
+ self.triples_N11 = [('ent#1234', ns.bse.iso, 123),
+ ('ent#4321', ns.bse.iso, 321)]
+ self.triples_N1U = [('ent#1234', ns.bse.tag, 'tag#1234'),
+ ('ent#1234', ns.bse.tag, 'tag#5678'),
+ ('ent#4321', ns.bse.tag, 'tag#4321')]
+ self.triples_NM1 = [('ent#1234', ns.bse.iso, 123),
+ ('ent#1234', ns.bse.t_created, '2010-01-02'),
+ ('ent#4321', ns.bse.iso, 321),
+ ('ent#4321', ns.bse.t_created, '2022-02-22')]
+ self.triples_NMU = [('ent#1234', ns.bse.iso, 123),
+ ('ent#1234', ns.bse.tag, 'tag#1234'),
+ ('ent#1234', ns.bse.tag, 'tag#5678'),
+ ('ent#4321', ns.bse.iso, 321),
+ ('ent#4321', ns.bse.t_created, '2022-02-22')]
+
+ def test_copy(self):
+ # iterator yields tuples
+ self.assertIsInstance(list(to_list_view([('subject', 'predicate', 'object')], node=True, path=True, value=True))[0], tuple)
+ # components are not changed
+ class Foo(): pass
+ foo = Foo()
+ self.assertListEqual(list(to_list_view([('subject', 'predicate', 'object')], node=True, path=True, value=True)),
+ [('subject', 'predicate', 'object')])
+ self.assertListEqual(list(to_list_view([(foo, 'predicate', 'object')], node=True, path=True, value=True)),
+ [(foo, 'predicate', 'object')])
+ self.assertListEqual(list(to_list_view([('subject', foo, 'object')], node=True, path=True, value=True)),
+ [('subject', foo, 'object')])
+ self.assertListEqual(list(to_list_view([('subject', 'predicate', foo)], node=True, path=True, value=True)),
+ [('subject', 'predicate', foo)])
+
+ def test_agg_none(self):
+ self.assertListEqual(list(to_list_view(self.triples_111, node=True, path=True, value=True)), self.triples_111)
+ self.assertListEqual(list(to_list_view(self.triples_11U, node=True, path=True, value=True)), self.triples_11U)
+ self.assertListEqual(list(to_list_view(self.triples_1M1, node=True, path=True, value=True)), self.triples_1M1)
+ self.assertListEqual(list(to_list_view(self.triples_1MU, node=True, path=True, value=True)), self.triples_1MU)
+ self.assertListEqual(list(to_list_view(self.triples_N11, node=True, path=True, value=True)), self.triples_N11)
+ self.assertListEqual(list(to_list_view(self.triples_N1U, node=True, path=True, value=True)), self.triples_N1U)
+ self.assertListEqual(list(to_list_view(self.triples_NM1, node=True, path=True, value=True)), self.triples_NM1)
+ self.assertListEqual(list(to_list_view(self.triples_NMU, node=True, path=True, value=True)), self.triples_NMU)
+
+ def test_agg_node(self):
+ self.assertListEqual(list(to_list_view(self.triples_111, node=False, path=True, value=True)),
+ [(ns.bse.iso, 123)])
+ self.assertListEqual(list(to_list_view(self.triples_11U, node=False, path=True, value=True)),
+ [(ns.bse.tag, 'tag#1234'), (ns.bse.tag, 'tag#5678')])
+ self.assertListEqual(list(to_list_view(self.triples_1M1, node=False, path=True, value=True)),
+ [(ns.bse.iso, 123), (ns.bse.t_created, '2010-01-02')])
+ self.assertListEqual(list(to_list_view(self.triples_1MU, node=False, path=True, value=True)),
+ [(ns.bse.iso, 123), (ns.bse.tag, 'tag#1234'), (ns.bse.tag, 'tag#5678')])
+ self.assertListEqual(list(to_list_view(self.triples_N11, node=False, path=True, value=True)),
+ [(ns.bse.iso, 123), (ns.bse.iso, 321)])
+ self.assertListEqual(list(to_list_view(self.triples_N1U, node=False, path=True, value=True)),
+ [(ns.bse.tag, 'tag#1234'), (ns.bse.tag, 'tag#5678'), (ns.bse.tag, 'tag#4321')])
+ self.assertListEqual(list(to_list_view(self.triples_NM1, node=False, path=True, value=True)),
+ [(ns.bse.iso, 123), (ns.bse.t_created, '2010-01-02'), (ns.bse.iso, 321), (ns.bse.t_created, '2022-02-22')])
+ self.assertListEqual(list(to_list_view(self.triples_NMU, node=False, path=True, value=True)),
+ [(ns.bse.iso, 123), (ns.bse.tag, 'tag#1234'), (ns.bse.tag, 'tag#5678'), (ns.bse.iso, 321), (ns.bse.t_created, '2022-02-22')])
+
+ def test_agg_path(self):
+ self.assertListEqual(list(to_list_view(self.triples_111, node=True, path=False, value=True)),
+ [('ent#1234', 123)])
+ self.assertListEqual(list(to_list_view(self.triples_11U, node=True, path=False, value=True)),
+ [('ent#1234', 'tag#1234'), ('ent#1234', 'tag#5678')])
+ self.assertListEqual(list(to_list_view(self.triples_1M1, node=True, path=False, value=True)),
+ [('ent#1234', 123), ('ent#1234', '2010-01-02')])
+ self.assertListEqual(list(to_list_view(self.triples_1MU, node=True, path=False, value=True)),
+ [('ent#1234', 123), ('ent#1234', 'tag#1234'), ('ent#1234', 'tag#5678')])
+ self.assertListEqual(list(to_list_view(self.triples_N11, node=True, path=False, value=True)),
+ [('ent#1234', 123), ('ent#4321', 321)])
+ self.assertListEqual(list(to_list_view(self.triples_N1U, node=True, path=False, value=True)),
+ [('ent#1234', 'tag#1234'), ('ent#1234', 'tag#5678'), ('ent#4321', 'tag#4321')])
+ self.assertListEqual(list(to_list_view(self.triples_NM1, node=True, path=False, value=True)),
+ [('ent#1234', 123), ('ent#1234', '2010-01-02'), ('ent#4321', 321), ('ent#4321', '2022-02-22')])
+ self.assertListEqual(list(to_list_view(self.triples_NMU, node=True, path=False, value=True)),
+ [('ent#1234', 123), ('ent#1234', 'tag#1234'), ('ent#1234', 'tag#5678'), ('ent#4321', 321), ('ent#4321', '2022-02-22')])
+
+ def test_agg_node_path(self):
+ self.assertListEqual(list(to_list_view(self.triples_111, node=False, path=False, value=True)),
+ [123])
+ self.assertListEqual(list(to_list_view(self.triples_11U, node=False, path=False, value=True)),
+ ['tag#1234', 'tag#5678'])
+ self.assertListEqual(list(to_list_view(self.triples_1M1, node=False, path=False, value=True)),
+ [123, '2010-01-02'])
+ self.assertListEqual(list(to_list_view(self.triples_1MU, node=False, path=False, value=True)),
+ [123, 'tag#1234', 'tag#5678'])
+ self.assertListEqual(list(to_list_view(self.triples_N11, node=False, path=False, value=True)),
+ [123, 321])
+ self.assertListEqual(list(to_list_view(self.triples_N1U, node=False, path=False, value=True)),
+ ['tag#1234', 'tag#5678', 'tag#4321'])
+ self.assertListEqual(list(to_list_view(self.triples_NM1, node=False, path=False, value=True)),
+ [123, '2010-01-02', 321, '2022-02-22'])
+ self.assertListEqual(list(to_list_view(self.triples_NMU, node=False, path=False, value=True)),
+ [123, 'tag#1234', 'tag#5678', 321, '2022-02-22'])
+
+ def test_agg_value(self):
+ # value flag has no effect
+ self.assertListEqual(list(to_list_view(self.triples_111, node=True, path=True, value=True)), self.triples_111)
+ self.assertListEqual(list(to_list_view(self.triples_11U, node=True, path=True, value=True)), self.triples_11U)
+ self.assertListEqual(list(to_list_view(self.triples_1M1, node=True, path=True, value=True)), self.triples_1M1)
+ self.assertListEqual(list(to_list_view(self.triples_1MU, node=True, path=True, value=True)), self.triples_1MU)
+ self.assertListEqual(list(to_list_view(self.triples_N11, node=True, path=True, value=True)), self.triples_N11)
+ self.assertListEqual(list(to_list_view(self.triples_N1U, node=True, path=True, value=True)), self.triples_N1U)
+ self.assertListEqual(list(to_list_view(self.triples_NM1, node=True, path=True, value=True)), self.triples_NM1)
+ self.assertListEqual(list(to_list_view(self.triples_NMU, node=True, path=True, value=True)), self.triples_NMU)
+
+ def test_agg_node_value(self):
+ # value flag has no effect -> same test as test_agg_node
+ self.assertListEqual(list(to_list_view(self.triples_111, node=False, path=True, value=False)),
+ [(ns.bse.iso, 123)])
+ self.assertListEqual(list(to_list_view(self.triples_11U, node=False, path=True, value=False)),
+ [(ns.bse.tag, 'tag#1234'), (ns.bse.tag, 'tag#5678')])
+ self.assertListEqual(list(to_list_view(self.triples_1M1, node=False, path=True, value=False)),
+ [(ns.bse.iso, 123), (ns.bse.t_created, '2010-01-02')])
+ self.assertListEqual(list(to_list_view(self.triples_1MU, node=False, path=True, value=False)),
+ [(ns.bse.iso, 123), (ns.bse.tag, 'tag#1234'), (ns.bse.tag, 'tag#5678')])
+ self.assertListEqual(list(to_list_view(self.triples_N11, node=False, path=True, value=False)),
+ [(ns.bse.iso, 123), (ns.bse.iso, 321)])
+ self.assertListEqual(list(to_list_view(self.triples_N1U, node=False, path=True, value=False)),
+ [(ns.bse.tag, 'tag#1234'), (ns.bse.tag, 'tag#5678'), (ns.bse.tag, 'tag#4321')])
+ self.assertListEqual(list(to_list_view(self.triples_NM1, node=False, path=True, value=False)),
+ [(ns.bse.iso, 123), (ns.bse.t_created, '2010-01-02'), (ns.bse.iso, 321), (ns.bse.t_created, '2022-02-22')])
+ self.assertListEqual(list(to_list_view(self.triples_NMU, node=False, path=True, value=False)),
+ [(ns.bse.iso, 123), (ns.bse.tag, 'tag#1234'), (ns.bse.tag, 'tag#5678'), (ns.bse.iso, 321), (ns.bse.t_created, '2022-02-22')])
+
+ def test_agg_path_value(self):
+ # value flag has no effect -> same test as test_agg_path
+ self.assertListEqual(list(to_list_view(self.triples_111, node=True, path=False, value=False)),
+ [('ent#1234', 123)])
+ self.assertListEqual(list(to_list_view(self.triples_11U, node=True, path=False, value=False)),
+ [('ent#1234', 'tag#1234'), ('ent#1234', 'tag#5678')])
+ self.assertListEqual(list(to_list_view(self.triples_1M1, node=True, path=False, value=False)),
+ [('ent#1234', 123), ('ent#1234', '2010-01-02')])
+ self.assertListEqual(list(to_list_view(self.triples_1MU, node=True, path=False, value=False)),
+ [('ent#1234', 123), ('ent#1234', 'tag#1234'), ('ent#1234', 'tag#5678')])
+ self.assertListEqual(list(to_list_view(self.triples_N11, node=True, path=False, value=False)),
+ [('ent#1234', 123), ('ent#4321', 321)])
+ self.assertListEqual(list(to_list_view(self.triples_N1U, node=True, path=False, value=False)),
+ [('ent#1234', 'tag#1234'), ('ent#1234', 'tag#5678'), ('ent#4321', 'tag#4321')])
+ self.assertListEqual(list(to_list_view(self.triples_NM1, node=True, path=False, value=False)),
+ [('ent#1234', 123), ('ent#1234', '2010-01-02'), ('ent#4321', 321), ('ent#4321', '2022-02-22')])
+ self.assertListEqual(list(to_list_view(self.triples_NMU, node=True, path=False, value=False)),
+ [('ent#1234', 123), ('ent#1234', 'tag#1234'), ('ent#1234', 'tag#5678'), ('ent#4321', 321), ('ent#4321', '2022-02-22')])
+
+ def test_agg_all(self):
+ # value flag has no effect -> same test as test_agg_node_path
+ self.assertListEqual(list(to_list_view(self.triples_111, node=False, path=False, value=False)),
+ [123])
+ self.assertListEqual(list(to_list_view(self.triples_11U, node=False, path=False, value=False)),
+ ['tag#1234', 'tag#5678'])
+ self.assertListEqual(list(to_list_view(self.triples_1M1, node=False, path=False, value=False)),
+ [123, '2010-01-02'])
+ self.assertListEqual(list(to_list_view(self.triples_1MU, node=False, path=False, value=False)),
+ [123, 'tag#1234', 'tag#5678'])
+ self.assertListEqual(list(to_list_view(self.triples_N11, node=False, path=False, value=False)),
+ [123, 321])
+ self.assertListEqual(list(to_list_view(self.triples_N1U, node=False, path=False, value=False)),
+ ['tag#1234', 'tag#5678', 'tag#4321'])
+ self.assertListEqual(list(to_list_view(self.triples_NM1, node=False, path=False, value=False)),
+ [123, '2010-01-02', 321, '2022-02-22'])
+ self.assertListEqual(list(to_list_view(self.triples_NMU, node=False, path=False, value=False)),
+ [123, 'tag#1234', 'tag#5678', 321, '2022-02-22'])
+
+
+class TestDictView(unittest.TestCase):
+ def setUp(self):
+ self.unique_paths = {ns.bse.iso, ns.bse.t_created}
+ self.triples_111 = [('ent#1234', ns.bse.iso, 123)]
+ self.triples_11U = [('ent#1234', ns.bse.tag, 'tag#1234'),
+ ('ent#1234', ns.bse.tag, 'tag#5678')]
+ self.triples_1M1 = [('ent#1234', ns.bse.iso, 123),
+ ('ent#1234', ns.bse.t_created, '2010-01-02')]
+ self.triples_1MU = [('ent#1234', ns.bse.iso, 123),
+ ('ent#1234', ns.bse.tag, 'tag#1234'),
+ ('ent#1234', ns.bse.tag, 'tag#5678')]
+ self.triples_N11 = [('ent#1234', ns.bse.iso, 123),
+ ('ent#4321', ns.bse.iso, 321)]
+ self.triples_N1U = [('ent#1234', ns.bse.tag, 'tag#1234'),
+ ('ent#1234', ns.bse.tag, 'tag#5678'),
+ ('ent#4321', ns.bse.tag, 'tag#4321')]
+ self.triples_NM1 = [('ent#1234', ns.bse.iso, 123),
+ ('ent#1234', ns.bse.t_created, '2010-01-02'),
+ ('ent#4321', ns.bse.iso, 321),
+ ('ent#4321', ns.bse.t_created, '2022-02-22')]
+ self.triples_NMU = [('ent#1234', ns.bse.iso, 123),
+ ('ent#1234', ns.bse.tag, 'tag#1234'),
+ ('ent#1234', ns.bse.tag, 'tag#5678'),
+ ('ent#4321', ns.bse.iso, 321),
+ ('ent#4321', ns.bse.t_created, '2022-02-22')]
+
+ def test_errounous_call(self):
+ # return set instead of value
+ self.assertSetEqual(to_dict_view(self.triples_111, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {123})
+ self.assertSetEqual(to_dict_view(self.triples_111, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {123})
+ # one_node mismatch: return set of values instead of value
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.iso: {123}})
+ # one_path mismatch: return set of values instead of value
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': {123}})
+ # unique_paths mismatch: return set of values instead of value
+ self.assertSetEqual(to_dict_view(self.triples_111, one_node=True, one_path=False, unique_paths={}, node=False, path=False, value=False),
+ {123})
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=False, unique_paths={}, node=False, path=True, value=False),
+ {ns.bse.iso: {123}})
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=False, unique_paths={}, node=True, path=False, value=False),
+ {'ent#1234': {123}})
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=False, unique_paths={}, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.iso: {123}}})
+
+ def test_agg_none(self):
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=True),
+ {'ent#1234': {ns.bse.iso: {123}}})
+ self.assertDictEqual(to_dict_view(self.triples_11U, one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=True),
+ {'ent#1234': {ns.bse.tag: {'tag#1234', 'tag#5678'}}})
+ self.assertDictEqual(to_dict_view(self.triples_1M1, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=True),
+ {'ent#1234': {ns.bse.iso: {123}, ns.bse.t_created: {'2010-01-02'}}})
+ self.assertDictEqual(to_dict_view(self.triples_1MU, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=True),
+ {'ent#1234': {ns.bse.iso: {123}, ns.bse.tag: {'tag#1234', 'tag#5678'}}})
+ self.assertDictEqual(to_dict_view(self.triples_N11, one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=True),
+ {'ent#1234': {ns.bse.iso: {123}}, 'ent#4321': {ns.bse.iso: {321}}})
+ self.assertDictEqual(to_dict_view(self.triples_N1U, one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=True),
+ {'ent#1234': {ns.bse.tag: {'tag#1234', 'tag#5678'}}, 'ent#4321': {ns.bse.tag: {'tag#4321'}}})
+ self.assertDictEqual(to_dict_view(self.triples_NM1, one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=True),
+ {'ent#1234': {ns.bse.iso: {123}, ns.bse.t_created: {'2010-01-02'}}, 'ent#4321': {ns.bse.iso: {321}, ns.bse.t_created: {'2022-02-22'}}})
+ self.assertDictEqual(to_dict_view(self.triples_NMU, one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=True),
+ {'ent#1234': {ns.bse.iso: {123}, ns.bse.tag: {'tag#1234', 'tag#5678'}}, 'ent#4321': {ns.bse.iso: {321}, ns.bse.t_created: {'2022-02-22'}}})
+ # empty
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=True), {})
+
+ def test_agg_node(self):
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=True),
+ {ns.bse.iso: {123}})
+ self.assertDictEqual(to_dict_view(self.triples_11U, one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=True),
+ {ns.bse.tag: {'tag#1234', 'tag#5678'}})
+ self.assertDictEqual(to_dict_view(self.triples_1M1, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=True),
+ {ns.bse.iso: {123}, ns.bse.t_created: {'2010-01-02'}})
+ self.assertDictEqual(to_dict_view(self.triples_1MU, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=True),
+ {ns.bse.iso: {123}, ns.bse.tag: {'tag#1234', 'tag#5678'}})
+ self.assertDictEqual(to_dict_view(self.triples_N11, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=True),
+ {ns.bse.iso: {123, 321}})
+ self.assertDictEqual(to_dict_view(self.triples_N1U, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=True),
+ {ns.bse.tag: {'tag#1234', 'tag#5678', 'tag#4321'}})
+ self.assertDictEqual(to_dict_view(self.triples_NM1, one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=True),
+ {ns.bse.iso: {123, 321}, ns.bse.t_created: {'2010-01-02', '2022-02-22'}})
+ self.assertDictEqual(to_dict_view(self.triples_NMU, one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=True),
+ {ns.bse.iso: {123, 321}, ns.bse.tag: {'tag#1234', 'tag#5678'}, ns.bse.t_created: {'2022-02-22'}})
+ # empty
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=True), {})
+
+ def test_agg_path(self):
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=True),
+ {'ent#1234': {123}})
+ self.assertDictEqual(to_dict_view(self.triples_11U, one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=True),
+ {'ent#1234': {'tag#1234', 'tag#5678'}})
+ self.assertDictEqual(to_dict_view(self.triples_1M1, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=True),
+ {'ent#1234': {123, '2010-01-02'}})
+ self.assertDictEqual(to_dict_view(self.triples_1MU, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=True),
+ {'ent#1234': {123, 'tag#1234', 'tag#5678'}})
+ self.assertDictEqual(to_dict_view(self.triples_N11, one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=True),
+ {'ent#1234': {123}, 'ent#4321': {321}})
+ self.assertDictEqual(to_dict_view(self.triples_N1U, one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=True),
+ {'ent#1234': {'tag#1234', 'tag#5678'}, 'ent#4321': {'tag#4321'}})
+ self.assertDictEqual(to_dict_view(self.triples_NM1, one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=True),
+ {'ent#1234': {123, '2010-01-02'}, 'ent#4321': {321, '2022-02-22'}})
+ self.assertDictEqual(to_dict_view(self.triples_NMU, one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=True),
+ {'ent#1234': {123, 'tag#1234', 'tag#5678'}, 'ent#4321': {321, '2022-02-22'}})
+ # empty
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=True), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=True), {})
+
+ def test_agg_node_path(self):
+ self.assertSetEqual(to_dict_view(self.triples_111, one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=True),
+ {123})
+ self.assertSetEqual(to_dict_view(self.triples_11U, one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=True),
+ {'tag#1234', 'tag#5678'})
+ self.assertSetEqual(to_dict_view(self.triples_1M1, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=True),
+ {123, '2010-01-02'})
+ self.assertSetEqual(to_dict_view(self.triples_1MU, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=True),
+ {123, 'tag#1234', 'tag#5678'})
+ self.assertSetEqual(to_dict_view(self.triples_N11, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=True),
+ {123, 321})
+ self.assertSetEqual(to_dict_view(self.triples_N1U, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=True),
+ {'tag#1234', 'tag#5678', 'tag#4321'})
+ self.assertSetEqual(to_dict_view(self.triples_NM1, one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=True),
+ {123, '2010-01-02', 321, '2022-02-22'})
+ self.assertSetEqual(to_dict_view(self.triples_NMU, one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=True),
+ {123, 'tag#1234', 'tag#5678', 321, '2022-02-22'})
+ # empty
+ self.assertSetEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=True), set())
+ self.assertSetEqual(to_dict_view([], one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=True), set())
+ self.assertSetEqual(to_dict_view([], one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=True), set())
+ self.assertSetEqual(to_dict_view([], one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=True), set())
+
+ def test_agg_value(self):
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.iso: 123}})
+ self.assertDictEqual(to_dict_view(self.triples_11U, one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.tag: {'tag#1234', 'tag#5678'}}})
+ self.assertDictEqual(to_dict_view(self.triples_1M1, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.iso: 123, ns.bse.t_created: '2010-01-02'}})
+ self.assertDictEqual(to_dict_view(self.triples_1MU, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.iso: 123, ns.bse.tag: {'tag#1234', 'tag#5678'}}})
+ self.assertDictEqual(to_dict_view(self.triples_N11, one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.iso: 123}, 'ent#4321': {ns.bse.iso: 321}})
+ self.assertDictEqual(to_dict_view(self.triples_N1U, one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.tag: {'tag#1234', 'tag#5678'}}, 'ent#4321': {ns.bse.tag: {'tag#4321'}}})
+ self.assertDictEqual(to_dict_view(self.triples_NM1, one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.iso: 123, ns.bse.t_created: '2010-01-02'}, 'ent#4321': {ns.bse.iso: 321, ns.bse.t_created: '2022-02-22'}})
+ self.assertDictEqual(to_dict_view(self.triples_NMU, one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=False),
+ {'ent#1234': {ns.bse.iso: 123, ns.bse.tag: {'tag#1234', 'tag#5678'}}, 'ent#4321': {ns.bse.iso: 321, ns.bse.t_created: '2022-02-22'}})
+ # empty
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=True, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=True, value=False), {})
+
+ def test_agg_node_value(self):
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.iso: 123})
+ self.assertDictEqual(to_dict_view(self.triples_11U, one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.tag: {'tag#1234', 'tag#5678'}})
+ self.assertDictEqual(to_dict_view(self.triples_1M1, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.iso: 123, ns.bse.t_created: '2010-01-02'})
+ self.assertDictEqual(to_dict_view(self.triples_1MU, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.iso: 123, ns.bse.tag: {'tag#1234', 'tag#5678'}})
+ self.assertDictEqual(to_dict_view(self.triples_N11, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.iso: {123, 321}})
+ self.assertDictEqual(to_dict_view(self.triples_N1U, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.tag: {'tag#1234', 'tag#5678', 'tag#4321'}})
+ self.assertDictEqual(to_dict_view(self.triples_NM1, one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.iso: {123, 321}, ns.bse.t_created: {'2010-01-02', '2022-02-22'}})
+ self.assertDictEqual(to_dict_view(self.triples_NMU, one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=False),
+ {ns.bse.iso: {123, 321}, ns.bse.tag: {'tag#1234', 'tag#5678'}, ns.bse.t_created: {'2022-02-22'}})
+ # empty
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=True, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=True, value=False), {})
+
+ def test_agg_path_value(self):
+ self.assertDictEqual(to_dict_view(self.triples_111, one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': 123})
+ self.assertDictEqual(to_dict_view(self.triples_11U, one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': {'tag#1234', 'tag#5678'}})
+ self.assertDictEqual(to_dict_view(self.triples_1M1, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': {123, '2010-01-02'}})
+ self.assertDictEqual(to_dict_view(self.triples_1MU, one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': {123, 'tag#1234', 'tag#5678'}})
+ self.assertDictEqual(to_dict_view(self.triples_N11, one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': 123, 'ent#4321': 321})
+ self.assertDictEqual(to_dict_view(self.triples_N1U, one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': {'tag#1234', 'tag#5678'}, 'ent#4321': {'tag#4321'}})
+ self.assertDictEqual(to_dict_view(self.triples_NM1, one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': {123, '2010-01-02'}, 'ent#4321': {321, '2022-02-22'}})
+ self.assertDictEqual(to_dict_view(self.triples_NMU, one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=False),
+ {'ent#1234': {123, 'tag#1234', 'tag#5678'}, 'ent#4321': {321, '2022-02-22'}})
+ # empty
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=True, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=True, unique_paths=self.unique_paths, node=True, path=False, value=False), {})
+ self.assertDictEqual(to_dict_view([], one_node=False, one_path=False, unique_paths=self.unique_paths, node=True, path=False, value=False), {})
+
+ def test_agg_all(self):
+ self.assertEqual(to_dict_view(self.triples_111, one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ 123)
+ self.assertSetEqual(to_dict_view(self.triples_11U, one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {'tag#1234', 'tag#5678'})
+ self.assertSetEqual(to_dict_view(self.triples_1M1, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {123, '2010-01-02'})
+ self.assertSetEqual(to_dict_view(self.triples_1MU, one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {123, 'tag#1234', 'tag#5678'})
+ self.assertSetEqual(to_dict_view(self.triples_N11, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {123, 321})
+ self.assertSetEqual(to_dict_view(self.triples_N1U, one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {'tag#1234', 'tag#5678', 'tag#4321'})
+ self.assertSetEqual(to_dict_view(self.triples_NM1, one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {123, '2010-01-02', 321, '2022-02-22'})
+ self.assertSetEqual(to_dict_view(self.triples_NMU, one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=False),
+ {123, 'tag#1234', 'tag#5678', 321, '2022-02-22'})
+ # empty
+ self.assertEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=False), None)
+ self.assertSetEqual(to_dict_view([], one_node=True, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=False), set())
+ self.assertSetEqual(to_dict_view([], one_node=False, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=False), set())
+ self.assertSetEqual(to_dict_view([], one_node=False, one_path=False, unique_paths=self.unique_paths, node=False, path=False, value=False), set())
+ self.assertEqual(to_dict_view([], one_node=True, one_path=True, unique_paths=self.unique_paths, node=False, path=False, value=False, default=123), 123)
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/graph/test_walk.py b/test/graph/test_walk.py
new file mode 100644
index 0000000..4b844da
--- /dev/null
+++ b/test/graph/test_walk.py
@@ -0,0 +1,170 @@
+
+# imports
+import unittest
+
+# bsfs imports
+from bsfs import schema as bsc
+from bsfs.graph import Graph
+from bsfs.graph.ac import NullAC
+from bsfs.namespace import Namespace, ns
+from bsfs.triple_store.sparql import SparqlStore
+from bsfs.utils import URI
+
+# symbol to test
+from bsfs.graph.walk import Walk
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+ns.bst = ns.bsfs.Tag()
+
+class TestWalk(unittest.TestCase):
+ def setUp(self):
+ # backend setup
+ self.schema = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/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 .
+
+ bse:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:User .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag .
+
+ bst:label rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range xsd:string ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bst:subTagOf rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Tag .
+
+ bst:main rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Entity .
+
+ bst:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range xsd:string .
+
+ ''')
+ self.backend = SparqlStore.Open()
+ self.user = URI('http://example.com/me')
+ self.ac = NullAC(self.backend, self.user)
+ self.graph = Graph(self.backend, self.ac)
+ self.graph.migrate(self.schema)
+
+ # nodes setup
+ self.ents = self.graph.nodes(ns.bsfs.Entity, {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321')})
+ self.tags = self.graph.nodes(ns.bsfs.Tag, {
+ URI('http://example.com/me/tag#1234'),
+ URI('http://example.com/me/tag#4321')})
+ # add some instances
+ self.ents.set(ns.bse.tag, self.tags)
+ self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#1234')).set(ns.bst.label, 'hello')
+ self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#4321')).set(ns.bst.label, 'world')
+
+ def test_essentials(self): # __eq__, __hash__, __str__, __repr__
+ p_author = self.schema.predicate(ns.bse.author)
+ p_tag = self.schema.predicate(ns.bse.tag)
+ p_main = self.schema.predicate(ns.bst.main)
+ # comparison
+ self.assertEqual(Walk(self.ents, [p_tag]), Walk(self.ents, [p_tag]))
+ self.assertEqual(hash(Walk(self.ents, [p_tag])), hash(Walk(self.ents, [p_tag])))
+ # comparison respects type
+ class Foo(Walk): pass
+ self.assertNotEqual(Walk(self.ents, [p_tag]), Foo(self.ents, [p_tag]))
+ self.assertNotEqual(hash(Walk(self.ents, [p_tag])), hash(Foo(self.ents, [p_tag])))
+ # comparison respects root
+ self.assertNotEqual(Walk(self.ents, [p_author]), Walk(self.tags, [p_author]))
+ self.assertNotEqual(hash(Walk(self.ents, [p_author])), hash(Walk(self.tags, [p_author])))
+ # comparison respects path
+ self.assertNotEqual(Walk(self.tags, [p_author]), Walk(self.tags, [p_main]))
+ self.assertNotEqual(hash(Walk(self.tags, [p_author])), hash(Walk(self.tags, [p_main])))
+ # string conversion
+ self.assertEqual(str(Walk(self.ents, [p_tag, p_main])),
+ 'Walk(@https://schema.bsfs.io/core/Entity: https://schema.bsfs.io/core/Entity#tag, https://schema.bsfs.io/core/Tag#main)')
+ self.assertEqual(repr(Walk(self.ents, [p_tag, p_main])),
+ 'Walk(https://schema.bsfs.io/core/Entity, (https://schema.bsfs.io/core/Entity#tag, https://schema.bsfs.io/core/Tag#main))')
+
+ def test_tail(self):
+ self.assertEqual(Walk(self.ents, (
+ self.schema.predicate(ns.bse.tag),
+ )).tail,
+ self.schema.node(ns.bsfs.Tag))
+ self.assertEqual(Walk(self.ents, (
+ self.schema.predicate(ns.bse.tag),
+ self.schema.predicate(ns.bst.main),
+ )).tail,
+ self.schema.node(ns.bsfs.Entity))
+
+ def test_step(self):
+ tag_type = self.schema.node(ns.bsfs.Tag)
+ # step returns a predicate
+ self.assertEqual(Walk.step(self.schema, tag_type, 'subTagOf'),
+ (self.schema.predicate(ns.bst.subTagOf), ))
+ # invalid step raises an error
+ self.assertRaises(ValueError, Walk.step, self.schema, tag_type, 'foobar')
+ # ambiguous step raises an error
+ self.assertRaises(ValueError, Walk.step, self.schema, tag_type, 'author')
+
+ def test_getattr(self): # __getattr__
+ walk = Walk(self.ents, (self.schema.predicate(ns.bse.tag), ))
+ # first step
+ self.assertEqual(walk.subTagOf, Walk(self.ents, (
+ self.schema.predicate(ns.bse.tag),
+ self.schema.predicate(ns.bst.subTagOf),
+ )))
+ # second step
+ self.assertEqual(walk.subTagOf.main, Walk(self.ents, (
+ self.schema.predicate(ns.bse.tag),
+ self.schema.predicate(ns.bst.subTagOf),
+ self.schema.predicate(ns.bst.main),
+ )))
+ # invalid step raises an error
+ self.assertRaises(ValueError, getattr, walk, 'foobar')
+ # ambiguous step raises an error
+ self.assertRaises(ValueError, getattr, walk, 'author')
+
+ def test_get(self): # get, __call__
+ walk = Walk(self.ents, (self.schema.predicate(ns.bse.tag), ))
+ tags = {
+ self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#1234')),
+ self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#4321'))}
+ # get returns from Nodes.get
+ self.assertDictEqual(walk.get(), {
+ self.graph.node(ns.bsfs.Entity, URI('http://example.com/me/entity#1234')): tags,
+ self.graph.node(ns.bsfs.Entity, URI('http://example.com/me/entity#4321')): tags,
+ })
+ self.assertDictEqual(walk(), {
+ self.graph.node(ns.bsfs.Entity, URI('http://example.com/me/entity#1234')): tags,
+ self.graph.node(ns.bsfs.Entity, URI('http://example.com/me/entity#4321')): tags,
+ })
+ # get passes kwargs to Nodes.get
+ self.assertSetEqual(tags, walk.get(node=False))
+ self.assertSetEqual(tags, walk(node=False))
+ self.assertSetEqual(tags, set(walk.get(view=list, node=False)))
+ self.assertSetEqual(tags, set(walk(view=list, node=False)))
+ # get returns values if need be
+ self.assertSetEqual(walk.label(node=False), {'hello', 'world'})
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/namespace/test_namespace.py b/test/namespace/test_namespace.py
index f109653..f7bf02a 100644
--- a/test/namespace/test_namespace.py
+++ b/test/namespace/test_namespace.py
@@ -1,9 +1,4 @@
-"""
-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
@@ -12,7 +7,7 @@ import unittest
from bsfs.utils import URI
# objects to test
-from bsfs.namespace.namespace import Namespace, ClosedNamespace
+from bsfs.namespace.namespace import Namespace, FinalNamespace
## code ##
@@ -20,108 +15,48 @@ from bsfs.namespace.namespace import Namespace, ClosedNamespace
class TestNamespace(unittest.TestCase):
def test_essentials(self):
# string conversion
- self.assertEqual(str(Namespace('http://example.org/')), 'Namespace(http://example.org)')
- self.assertEqual(str(Namespace('http://example.org#')), 'Namespace(http://example.org)')
- self.assertEqual(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, #, /)')
+ self.assertEqual(str(Namespace('http://example.org')), 'http://example.org')
+ self.assertEqual(repr(Namespace('http://example.org')), "'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.assertEqual(Namespace('http://example.org'), Namespace('http://example.org'))
+ self.assertEqual(hash(Namespace('http://example.org')), hash(Namespace('http://example.org')))
+ # Namespace compares to string
+ self.assertEqual(Namespace('http://example.org'), 'http://example.org')
+ self.assertEqual(hash(Namespace('http://example.org')), hash('http://example.org'))
+ # URI must match
+ self.assertNotEqual(Namespace('http://example.org'), Namespace('http://example.com'))
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')
+ self.assertEqual(Namespace('http://example.org').foo, Namespace('http://example.org/foo'))
+ self.assertEqual(Namespace('http://example.org').bar, Namespace('http://example.org/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_call(self):
+ self.assertEqual(Namespace('http://example.org')(), FinalNamespace('http://example.org', sep='#'))
+ self.assertEqual(Namespace('http://example.org').foo(), FinalNamespace('http://example.org/foo', sep='#'))
- 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):
+class TestFinalNamespace(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, #, .)')
+ # string conversion
+ self.assertEqual(str(FinalNamespace('http://example.org')), 'http://example.org')
+ self.assertEqual(repr(FinalNamespace('http://example.org')), "'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/')))
+ self.assertEqual(FinalNamespace('http://example.org'), FinalNamespace('http://example.org'))
+ self.assertEqual(hash(FinalNamespace('http://example.org')), hash(FinalNamespace('http://example.org')))
+ # FinalNamespace compares to string
+ self.assertEqual(FinalNamespace('http://example.org'), 'http://example.org')
+ self.assertEqual(hash(FinalNamespace('http://example.org')), hash('http://example.org'))
+ # URI must match
+ self.assertNotEqual(FinalNamespace('http://example.org'), FinalNamespace('http://example.com'))
+ self.assertNotEqual(hash(FinalNamespace('http://example.org')), hash(FinalNamespace('http://example.com')))
+ # separator is ignored
+ self.assertEqual(FinalNamespace('http://example.org'), FinalNamespace('http://example.org', sep='/'))
+ self.assertEqual(hash(FinalNamespace('http://example.org')), hash(FinalNamespace('http://example.org', sep='/')))
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')
+ self.assertEqual(FinalNamespace('http://example.org').foo, FinalNamespace('http://example.org#foo'))
+ self.assertEqual(FinalNamespace('http://example.org').bar, FinalNamespace('http://example.org#bar'))
+ self.assertEqual(FinalNamespace('http://example.org', sep='/').bar, FinalNamespace('http://example.org/bar'))
## main ##
diff --git a/test/query/__init__.py b/test/query/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/query/__init__.py
diff --git a/test/query/ast_test/__init__.py b/test/query/ast_test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/query/ast_test/__init__.py
diff --git a/test/query/ast_test/test_fetch.py b/test/query/ast_test/test_fetch.py
new file mode 100644
index 0000000..ccb680e
--- /dev/null
+++ b/test/query/ast_test/test_fetch.py
@@ -0,0 +1,234 @@
+
+# imports
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.query.ast.fetch import FetchExpression
+from bsfs.query.ast.fetch import All, This
+from bsfs.query.ast.fetch import _Branch, Fetch
+from bsfs.query.ast.fetch import _Named, Node, Value
+
+
+## code ##
+
+class TestExpression(unittest.TestCase): # FetchExpression
+ def test_essentials(self):
+ class Foo(FetchExpression): pass
+ # comparison
+ self.assertEqual(FetchExpression(), FetchExpression())
+ self.assertEqual(hash(FetchExpression()), hash(FetchExpression()))
+ # comparison respects type
+ self.assertNotEqual(FetchExpression(), Foo())
+ self.assertNotEqual(hash(FetchExpression()), hash(Foo()))
+ # string conversion
+ self.assertEqual(str(FetchExpression()), 'FetchExpression()')
+ self.assertEqual(repr(FetchExpression()), 'FetchExpression()')
+ self.assertEqual(str(Foo()), 'Foo()')
+ self.assertEqual(repr(Foo()), 'Foo()')
+
+
+class TestAll(unittest.TestCase): # All
+ def test_essentials(self):
+ class Foo(All): pass
+ expr0 = This('hello')
+ expr1 = This('world')
+ # comparison
+ self.assertEqual(All(expr0), All(expr0))
+ self.assertEqual(hash(All(expr0)), hash(All(expr0)))
+ # comparison respects type
+ self.assertNotEqual(All(expr0), Foo(expr0))
+ self.assertNotEqual(hash(All(expr0)), hash(Foo(expr0)))
+ # comparison respects expressions
+ self.assertEqual(All(expr0, expr1), All(expr0, expr1))
+ self.assertEqual(hash(All(expr0, expr1)), hash(All(expr0, expr1)))
+ self.assertNotEqual(All(expr0), All(expr1))
+ self.assertNotEqual(hash(All(expr0)), hash(All(expr1)))
+ # expressions are unordered
+ self.assertEqual(All(expr0, expr1), All(expr1, expr0))
+ self.assertEqual(hash(All(expr0, expr1)), hash(All(expr1, expr0)))
+ # string conversion
+ self.assertIn(str(All(expr0, expr1)), {
+ 'All({This(world), This(hello)})',
+ 'All({This(hello), This(world)})'})
+ self.assertIn(repr(All(expr0, expr1)), {
+ 'All({This(world), This(hello)})',
+ 'All({This(hello), This(world)})'})
+
+ def test_members(self):
+ class Foo(): pass
+ expr0 = This('hello')
+ expr1 = This('world')
+ # requires at least one child expression
+ self.assertRaises(AttributeError, All)
+ # expr returns child expressions
+ self.assertEqual(All(expr0, expr1).expr, {expr0, expr1})
+ # can pass expressions as arguments
+ self.assertEqual(All(expr0, expr1).expr, {expr0, expr1})
+ # can pass a single expression as argument
+ self.assertEqual(All(expr0).expr, {expr0})
+ # can pass expressions as list-like
+ self.assertEqual(All([expr0, expr1]).expr, {expr0, expr1})
+ self.assertEqual(All((expr0, expr1)).expr, {expr0, expr1})
+ self.assertEqual(All({expr0, expr1}).expr, {expr0, expr1})
+ # can pass a single expression as list-like
+ self.assertEqual(All([expr0]).expr, {expr0})
+ # must pass a FilterExpression
+ self.assertRaises(TypeError, All, Foo())
+ self.assertRaises(TypeError, All, 1234)
+ self.assertRaises(TypeError, All, 'hello world')
+ # len returns the number of child expressions
+ self.assertEqual(len(All(expr0)), 1)
+ self.assertEqual(len(All(expr0, expr1)), 2)
+ # iter iterates over child expressions
+ self.assertSetEqual(set(All(expr0, expr1)), {expr0, expr1})
+
+
+class TestThis(unittest.TestCase): # This
+ def test_essentials(self):
+ class Foo(This): pass
+ # comparison
+ self.assertEqual(This('hello'), This('hello'))
+ self.assertEqual(hash(This('hello')), hash(This('hello')))
+ # comparison respects type
+ self.assertNotEqual(This('hello'), Foo('hello'))
+ self.assertNotEqual(hash(This('hello')), hash(Foo('hello')))
+ # comparison respects name
+ self.assertNotEqual(This('hello'), This('world'))
+ self.assertNotEqual(hash(This('hello')), hash(This('world')))
+ # string conversion
+ self.assertEqual(str(This('hello')), 'This(hello)')
+ self.assertEqual(repr(This('hello')), 'This(hello)')
+
+ def test_members(self):
+ class Foo(): pass
+ # name returns member
+ self.assertEqual(This('hello').name, 'hello')
+ self.assertEqual(This('world').name, 'world')
+ # name is converted to a string
+ self.assertEqual(This(1234).name, '1234')
+ foo = Foo()
+ self.assertEqual(This(foo).name, str(foo))
+
+
+class TestBranch(unittest.TestCase): # _Branch, Fetch
+ def test_essentials(self):
+ pred = ns.bse.tag
+ expr = FetchExpression()
+ # comparison
+ self.assertEqual(_Branch(pred), _Branch(pred))
+ self.assertEqual(hash(_Branch(pred)), hash(_Branch(pred)))
+ self.assertEqual(Fetch(pred, expr), Fetch(pred, expr))
+ self.assertEqual(hash(Fetch(pred, expr)), hash(Fetch(pred, expr)))
+ # comparison respects type
+ self.assertNotEqual(_Branch(pred), Fetch(pred, expr))
+ self.assertNotEqual(hash(_Branch(pred)), hash(Fetch(pred, expr)))
+ self.assertNotEqual(Fetch(pred, expr), _Branch(pred))
+ self.assertNotEqual(hash(Fetch(pred, expr)), hash(_Branch(pred)))
+ # comparison respects predicate
+ self.assertNotEqual(_Branch(pred), _Branch(ns.bse.filesize))
+ self.assertNotEqual(hash(_Branch(pred)), hash(_Branch(ns.bse.filesize)))
+ self.assertNotEqual(Fetch(pred, expr), Fetch(ns.bse.filesize, expr))
+ self.assertNotEqual(hash(Fetch(pred, expr)), hash(Fetch(ns.bse.filesize, expr)))
+ # comparison respects expression
+ self.assertNotEqual(Fetch(pred, expr), Fetch(pred, This('foo')))
+ self.assertNotEqual(hash(Fetch(pred, expr)), hash(Fetch(pred, This('foo'))))
+ # string conversion
+ self.assertEqual(str(_Branch(pred)), f'_Branch({pred})')
+ self.assertEqual(repr(_Branch(pred)), f'_Branch({pred})')
+ self.assertEqual(str(Fetch(pred, expr)), f'Fetch({pred}, {expr})')
+ self.assertEqual(repr(Fetch(pred, expr)), f'Fetch({pred}, {expr})')
+
+ def test_members(self):
+ class Foo(): pass
+ pred = ns.bse.tag
+ expr = FetchExpression()
+
+ # predicate returns member
+ self.assertEqual(_Branch(pred).predicate, pred)
+ self.assertEqual(Fetch(pred, expr).predicate, pred)
+ # can pass an URI
+ self.assertEqual(_Branch(ns.bse.filename).predicate, ns.bse.filename)
+ self.assertEqual(Fetch(ns.bse.filename, expr).predicate, ns.bse.filename)
+ # must pass an URI
+ self.assertRaises(TypeError, _Branch, Foo())
+ self.assertRaises(TypeError, Fetch, Foo(), expr)
+ # expression returns member
+ self.assertEqual(Fetch(pred, expr).expr, expr)
+ # expression must be a FilterExpression
+ self.assertRaises(TypeError, Fetch, ns.bse.filename, 'hello')
+ self.assertRaises(TypeError, Fetch, ns.bse.filename, 1234)
+ self.assertRaises(TypeError, Fetch, ns.bse.filename, Foo())
+
+
+class TestNamed(unittest.TestCase): # _Named, Node, Value
+ def test_essentials(self):
+ pred = ns.bse.tag
+ name = 'foobar'
+ # comparison
+ self.assertEqual(_Named(pred, name), _Named(pred, name))
+ self.assertEqual(hash(_Named(pred, name)), hash(_Named(pred, name)))
+ # comparison respects type
+ self.assertNotEqual(_Named(pred, name), Node(pred, name))
+ self.assertNotEqual(Node(pred, name), Value(pred, name))
+ self.assertNotEqual(Value(pred, name), _Named(pred, name))
+ self.assertNotEqual(hash(_Named(pred, name)), hash(Node(pred, name)))
+ self.assertNotEqual(hash(Node(pred, name)), hash(Value(pred, name)))
+ self.assertNotEqual(hash(Value(pred, name)), hash(_Named(pred, name)))
+ # comparison respects predicate
+ self.assertNotEqual(_Named(pred, name), _Named(ns.bse.filesize, name))
+ self.assertNotEqual(hash(_Named(pred, name)), hash(_Named(ns.bse.filesize, name)))
+ self.assertNotEqual(Node(pred, name), Node(ns.bse.filesize, name))
+ self.assertNotEqual(hash(Node(pred, name)), hash(Node(ns.bse.filesize, name)))
+ self.assertNotEqual(Value(pred, name), Value(ns.bse.filesize, name))
+ self.assertNotEqual(hash(Value(pred, name)), hash(Value(ns.bse.filesize, name)))
+ # comparison respects name
+ self.assertNotEqual(_Named(pred, name), _Named(pred, 'foo'))
+ self.assertNotEqual(hash(_Named(pred, name)), hash(_Named(pred, 'foo')))
+ self.assertNotEqual(Node(pred, name), Node(pred, 'foo'))
+ self.assertNotEqual(hash(Node(pred, name)), hash(Node(pred, 'foo')))
+ self.assertNotEqual(Value(pred, name), Value(pred, 'foo'))
+ self.assertNotEqual(hash(Value(pred, name)), hash(Value(pred, 'foo')))
+ # string conversion
+ self.assertEqual(str(_Named(pred, name)), f'_Named({pred}, {name})')
+ self.assertEqual(repr(_Named(pred, name)), f'_Named({pred}, {name})')
+ self.assertEqual(str(Node(pred, name)), f'Node({pred}, {name})')
+ self.assertEqual(repr(Node(pred, name)), f'Node({pred}, {name})')
+ self.assertEqual(str(Value(pred, name)), f'Value({pred}, {name})')
+ self.assertEqual(repr(Value(pred, name)), f'Value({pred}, {name})')
+
+ def test_members(self):
+ class Foo(): pass
+ pred = ns.bse.tag
+ name = 'foobar'
+ # predicate returns member
+ self.assertEqual(_Named(pred, name).predicate, pred)
+ self.assertEqual(Node(pred, name).predicate, pred)
+ self.assertEqual(Value(pred, name).predicate, pred)
+ # can pass an URI as predicate
+ self.assertEqual(_Named(ns.bse.filename, name).predicate, ns.bse.filename)
+ self.assertEqual(Node(ns.bse.filename, name).predicate, ns.bse.filename)
+ self.assertEqual(Value(ns.bse.filename, name).predicate, ns.bse.filename)
+ # must pass an URI
+ self.assertRaises(TypeError, _Named, Foo(), name)
+ self.assertRaises(TypeError, Node, Foo(), name)
+ self.assertRaises(TypeError, Value, Foo(), name)
+ # name returns member
+ self.assertEqual(_Named(pred, name).name, name)
+ self.assertEqual(Node(pred, name).name, name)
+ self.assertEqual(Value(pred, name).name, name)
+ # name is converted to a string
+ self.assertEqual(_Named(pred, 1234).name, '1234')
+ self.assertEqual(Node(pred, 1234).name, '1234')
+ self.assertEqual(Value(pred, 1234).name, '1234')
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/query/ast_test/test_filter_.py b/test/query/ast_test/test_filter_.py
new file mode 100644
index 0000000..d0d42ea
--- /dev/null
+++ b/test/query/ast_test/test_filter_.py
@@ -0,0 +1,614 @@
+
+# imports
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.query.ast.filter_ import _Expression, FilterExpression, PredicateExpression
+from bsfs.query.ast.filter_ import _Branch, Any, All
+from bsfs.query.ast.filter_ import _Agg, And, Or
+from bsfs.query.ast.filter_ import Not, Has, Distance
+from bsfs.query.ast.filter_ import _Value, Is, Equals, Substring, StartsWith, EndsWith
+from bsfs.query.ast.filter_ import _Bounded, LessThan, GreaterThan
+from bsfs.query.ast.filter_ import Predicate, OneOf
+from bsfs.query.ast.filter_ import IsIn, IsNotIn
+from bsfs.query.ast.filter_ import Includes, Excludes, Between
+
+
+## code ##
+
+class TestExpression(unittest.TestCase):
+ def test_essentials(self):
+ # comparison
+ self.assertEqual(_Expression(), _Expression())
+ self.assertEqual(FilterExpression(), FilterExpression())
+ self.assertEqual(PredicateExpression(), PredicateExpression())
+ self.assertEqual(hash(_Expression()), hash(_Expression()))
+ self.assertEqual(hash(FilterExpression()), hash(FilterExpression()))
+ self.assertEqual(hash(PredicateExpression()), hash(PredicateExpression()))
+ # comparison respects type
+ self.assertNotEqual(FilterExpression(), _Expression())
+ self.assertNotEqual(_Expression(), PredicateExpression())
+ self.assertNotEqual(PredicateExpression(), FilterExpression())
+ self.assertNotEqual(hash(FilterExpression()), hash(_Expression()))
+ self.assertNotEqual(hash(_Expression()), hash(PredicateExpression()))
+ self.assertNotEqual(hash(PredicateExpression()), hash(FilterExpression()))
+ # string conversion
+ self.assertEqual(str(_Expression()), '_Expression()')
+ self.assertEqual(str(FilterExpression()), 'FilterExpression()')
+ self.assertEqual(str(PredicateExpression()), 'PredicateExpression()')
+ self.assertEqual(repr(_Expression()), '_Expression()')
+ self.assertEqual(repr(FilterExpression()), 'FilterExpression()')
+ self.assertEqual(repr(PredicateExpression()), 'PredicateExpression()')
+
+
+class TestBranch(unittest.TestCase): # _Branch, Any, All
+ def test_essentials(self):
+ pred = PredicateExpression()
+ expr = FilterExpression()
+
+ # comparison respects type
+ self.assertNotEqual(_Branch(pred, expr), Any(pred, expr))
+ self.assertNotEqual(Any(pred, expr), All(pred, expr))
+ self.assertNotEqual(All(pred, expr), _Branch(pred, expr))
+ self.assertNotEqual(hash(_Branch(pred, expr)), hash(Any(pred, expr)))
+ self.assertNotEqual(hash(Any(pred, expr)), hash(All(pred, expr)))
+ self.assertNotEqual(hash(All(pred, expr)), hash(_Branch(pred, expr)))
+
+ for cls in (_Branch, Any, All):
+ # comparison
+ self.assertEqual(cls(pred, expr), cls(pred, expr))
+ self.assertEqual(hash(cls(pred, expr)), hash(cls(pred, expr)))
+ # comparison respects predicate
+ self.assertNotEqual(cls(ns.bse.filename, expr), cls(ns.bse.filesize, expr))
+ self.assertNotEqual(hash(cls(ns.bse.filename, expr)), hash(cls(ns.bse.filesize, expr)))
+ # comparison respects expression
+ self.assertNotEqual(cls(pred, Equals('hello')), cls(pred, Equals('world')))
+ self.assertNotEqual(hash(cls(pred, Equals('hello'))), hash(cls(pred, Equals('world'))))
+
+ # string conversion
+ self.assertEqual(str(_Branch(pred, expr)), f'_Branch({pred}, {expr})')
+ self.assertEqual(repr(_Branch(pred, expr)), f'_Branch({pred}, {expr})')
+ self.assertEqual(str(Any(pred, expr)), f'Any({pred}, {expr})')
+ self.assertEqual(repr(Any(pred, expr)), f'Any({pred}, {expr})')
+ self.assertEqual(str(All(pred, expr)), f'All({pred}, {expr})')
+ self.assertEqual(repr(All(pred, expr)), f'All({pred}, {expr})')
+
+ def test_members(self):
+ class Foo(): pass
+ pred = PredicateExpression()
+ expr = FilterExpression()
+
+ for cls in (_Branch, Any, All):
+ # predicate returns member
+ self.assertEqual(cls(PredicateExpression(), expr).predicate, PredicateExpression())
+ # can pass an URI
+ self.assertEqual(cls(ns.bse.filename, expr).predicate, Predicate(ns.bse.filename))
+ # can pass a PredicateExpression
+ self.assertEqual(cls(Predicate(ns.bse.filename), expr).predicate, Predicate(ns.bse.filename))
+ # must pass an URI or PredicateExpression
+ self.assertRaises(TypeError, cls, Foo(), expr)
+ # expression returns member
+ self.assertEqual(cls(pred, Equals('hello')).expr, Equals('hello'))
+ # expression must be a FilterExpression
+ self.assertRaises(TypeError, cls, ns.bse.filename, 'hello')
+ self.assertRaises(TypeError, cls, ns.bse.filename, 1234)
+ self.assertRaises(TypeError, cls, ns.bse.filename, Foo())
+
+
+class TestAgg(unittest.TestCase): # _Agg, And, Or
+ def test_essentials(self):
+ expr = {Equals('hello'), Equals('world')}
+
+ # comparison respects type
+ self.assertNotEqual(_Agg(expr), And(expr))
+ self.assertNotEqual(And(expr), Or(expr))
+ self.assertNotEqual(Or(expr), _Agg(expr))
+ self.assertNotEqual(hash(_Agg(expr)), hash(And(expr)))
+ self.assertNotEqual(hash(And(expr)), hash(Or(expr)))
+ self.assertNotEqual(hash(Or(expr)), hash(_Agg(expr)))
+
+ for cls in (_Agg, And, Or):
+ # comparison
+ self.assertEqual(cls(expr), cls(expr))
+ self.assertEqual(hash(cls(expr)), hash(cls(expr)))
+ # comparison respects expression
+ self.assertNotEqual(cls(expr), cls(Equals('world')))
+ self.assertNotEqual(hash(cls(expr)), hash(cls(Equals('world'))))
+ self.assertNotEqual(cls(Equals('hello')), cls(Equals('world')))
+ self.assertNotEqual(hash(cls(Equals('hello'))), hash(cls(Equals('world'))))
+
+ # string conversion
+ self.assertEqual(str(_Agg(Equals('hello'))), '_Agg({Equals(hello)})')
+ self.assertEqual(repr(_Agg(Equals('hello'))), '_Agg({Equals(hello)})')
+ self.assertEqual(str(And(Equals('hello'))), 'And({Equals(hello)})')
+ self.assertEqual(repr(And(Equals('hello'))), 'And({Equals(hello)})')
+ self.assertEqual(str(Or(Equals('hello'))), 'Or({Equals(hello)})')
+ self.assertEqual(repr(Or(Equals('hello'))), 'Or({Equals(hello)})')
+
+ def test_expression(self):
+ class Foo(): pass
+
+ for cls in (_Agg, And, Or):
+ # can pass expressions as arguments
+ self.assertSetEqual(cls(Equals('hello'), Equals('world')).expr, {Equals('hello'), Equals('world')})
+ # can pass one expressions as argument
+ self.assertSetEqual(cls(Equals('hello')).expr, {Equals('hello')})
+ # can pass expressions as iterator
+ self.assertSetEqual(cls(iter((Equals('hello'), Equals('world')))).expr, {Equals('hello'), Equals('world')})
+ # can pass expressions as generator
+ def gen():
+ yield Equals('hello')
+ yield Equals('world')
+ self.assertSetEqual(cls(gen()).expr, {Equals('hello'), Equals('world')})
+ # can pass expressions as list-like
+ self.assertSetEqual(cls((Equals('hello'), Equals('world'))).expr, {Equals('hello'), Equals('world')})
+ # can pass one expression as list-like
+ self.assertSetEqual(cls([Equals('hello')]).expr, {Equals('hello')})
+ # must pass expressions
+ self.assertRaises(TypeError, cls, Foo(), Foo())
+ self.assertRaises(TypeError, cls, [Foo(), Foo()])
+
+ # iter
+ self.assertSetEqual(set(iter(cls(Equals('hello'), Equals('world')))), {Equals('hello'), Equals('world')})
+ # contains
+ self.assertIn(Equals('world'), cls(Equals('hello'), Equals('world')))
+ self.assertNotIn(Equals('foo'), cls(Equals('hello'), Equals('world')))
+ # len
+ self.assertEqual(len(cls(Equals('hello'), Equals('world'))), 2)
+ self.assertEqual(len(cls(Equals('hello'), Equals('world'), Equals('foo'))), 3)
+
+
+
+class TestNot(unittest.TestCase):
+ def test_essentials(self):
+ expr = FilterExpression()
+ # comparison
+ self.assertEqual(Not(expr), Not(expr))
+ self.assertEqual(hash(Not(expr)), hash(Not(expr)))
+ # comparison respects type
+ self.assertNotEqual(Not(expr), FilterExpression())
+ self.assertNotEqual(hash(Not(expr)), hash(FilterExpression()))
+ # comparison respects expression
+ self.assertNotEqual(Not(Equals('hello')), Not(Equals('world')))
+ self.assertNotEqual(hash(Not(Equals('hello'))), hash(Not(Equals('world'))))
+ # string conversion
+ self.assertEqual(str(Not(Equals('hello'))), 'Not(Equals(hello))')
+ self.assertEqual(repr(Not(Equals('hello'))), 'Not(Equals(hello))')
+
+ def test_expression(self):
+ # Not requires an expression argument
+ self.assertRaises(TypeError, Not)
+ # expression must be a FilterExpression
+ self.assertRaises(TypeError, Not, 'hello')
+ self.assertRaises(TypeError, Not, 1234)
+ self.assertRaises(TypeError, Not, Predicate(ns.bse.filesize))
+ # member returns expression
+ self.assertEqual(Not(Equals('hello')).expr, Equals('hello'))
+
+
+class TestHas(unittest.TestCase):
+ def test_essentials(self):
+ pred = PredicateExpression()
+ count = FilterExpression()
+ # comparison
+ self.assertEqual(Has(pred, count), Has(pred, count))
+ self.assertEqual(hash(Has(pred, count)), hash(Has(pred, count)))
+ # comparison respects type
+ self.assertNotEqual(Has(pred, count), FilterExpression())
+ self.assertNotEqual(hash(Has(pred, count)), hash(FilterExpression()))
+ # comparison respects predicate
+ self.assertNotEqual(Has(pred, count), Has(Predicate(ns.bse.filesize), count))
+ self.assertNotEqual(hash(Has(pred, count)), hash(Has(Predicate(ns.bse.filesize), count)))
+ # comparison respects count
+ self.assertNotEqual(Has(pred, count), Has(pred, LessThan(5)))
+ self.assertNotEqual(hash(Has(pred, count)), hash(Has(pred, LessThan(5))))
+ # string conversion
+ self.assertEqual(str(Has(Predicate(ns.bse.filesize), LessThan(5))),
+ f'Has(Predicate({ns.bse.filesize}, False), LessThan(5.0, True))')
+ self.assertEqual(repr(Has(Predicate(ns.bse.filesize), LessThan(5))),
+ f'Has(Predicate({ns.bse.filesize}, False), LessThan(5.0, True))')
+
+ def test_members(self):
+ pred = PredicateExpression()
+ count = FilterExpression()
+ # member returns expression
+ # predicate must be an URI or a PredicateExpression
+ self.assertEqual(Has(ns.bse.filesize, count).predicate, Predicate(ns.bse.filesize))
+ self.assertEqual(Has(Predicate(ns.bse.filesize), count).predicate, Predicate(ns.bse.filesize))
+ self.assertRaises(TypeError, Has, 1234, FilterExpression())
+ self.assertRaises(TypeError, Has, FilterExpression(), FilterExpression())
+ # member returns count
+ # count must be None, an integer, or a FilterExpression
+ self.assertEqual(Has(pred).count, GreaterThan(1, False))
+ self.assertEqual(Has(pred, LessThan(5)).count, LessThan(5))
+ self.assertEqual(Has(pred, 5).count, Equals(5))
+ self.assertRaises(TypeError, Has, pred, 'hello')
+ self.assertRaises(TypeError, Has, pred, Predicate(ns.bse.filesize))
+
+
+
+class TestValue(unittest.TestCase):
+ def test_essentials(self):
+ # comparison respects type
+ self.assertNotEqual(_Value('hello'), Equals('hello'))
+ self.assertNotEqual(Equals('hello'), Is('hello'))
+ self.assertNotEqual(Is('hello'), Substring('hello'))
+ self.assertNotEqual(Substring('hello'), StartsWith('hello'))
+ self.assertNotEqual(StartsWith('hello'), EndsWith('hello'))
+ self.assertNotEqual(EndsWith('hello'), _Value('hello'))
+ self.assertNotEqual(hash(_Value('hello')), hash(Equals('hello')))
+ self.assertNotEqual(hash(Equals('hello')), hash(Is('hello')))
+ self.assertNotEqual(hash(Is('hello')), hash(Substring('hello')))
+ self.assertNotEqual(hash(Substring('hello')), hash(StartsWith('hello')))
+ self.assertNotEqual(hash(StartsWith('hello')), hash(EndsWith('hello')))
+ self.assertNotEqual(hash(EndsWith('hello')), hash(_Value('hello')))
+
+ for cls in (_Value, Is, Equals, Substring, StartsWith, EndsWith):
+ # comparison
+ self.assertEqual(cls('hello'), cls('hello'))
+ self.assertEqual(hash(cls('hello')), hash(cls('hello')))
+ # comparison respects value
+ self.assertNotEqual(cls('hello'), cls('world'))
+ self.assertNotEqual(hash(cls('hello')), hash(cls('world')))
+
+ # string conversion
+ self.assertEqual(str(_Value('hello')), '_Value(hello)')
+ self.assertEqual(repr(_Value('hello')), '_Value(hello)')
+ self.assertEqual(str(Is('hello')), 'Is(hello)')
+ self.assertEqual(repr(Is('hello')), 'Is(hello)')
+ self.assertEqual(str(Equals('hello')), 'Equals(hello)')
+ self.assertEqual(repr(Equals('hello')), 'Equals(hello)')
+ self.assertEqual(str(Substring('hello')), 'Substring(hello)')
+ self.assertEqual(repr(Substring('hello')), 'Substring(hello)')
+ self.assertEqual(str(StartsWith('hello')), 'StartsWith(hello)')
+ self.assertEqual(repr(StartsWith('hello')), 'StartsWith(hello)')
+ self.assertEqual(str(EndsWith('hello')), 'EndsWith(hello)')
+ self.assertEqual(repr(EndsWith('hello')), 'EndsWith(hello)')
+
+ def test_value(self):
+ class Foo(): pass
+ for cls in (_Value, Is, Equals, Substring, StartsWith, EndsWith):
+ # value can be anything
+ # value returns member
+ f = Foo()
+ self.assertEqual(cls('hello').value, 'hello')
+ self.assertEqual(cls(1234).value, 1234)
+ self.assertEqual(cls(f).value, f)
+
+
+class TestDistance(unittest.TestCase):
+ def test_essentials(self):
+ ref = (1,2,3)
+ # comparison
+ self.assertEqual(Distance(ref, 3), Distance(ref, 3))
+ self.assertEqual(hash(Distance(ref, 3)), hash(Distance(ref, 3)))
+ # comparison respects type
+ self.assertNotEqual(Distance(ref, 3), FilterExpression())
+ self.assertNotEqual(hash(Distance(ref, 3)), hash(FilterExpression()))
+ # comparison respects reference
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2), 3, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2), 3, False)))
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,5,3), 3, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,5,3), 3, False)))
+ # comparison respects threshold
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2,3), 3.1, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2,3), 3.1, False)))
+ # comparison respects strict flag
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2,3), 3, True))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2,3), 3, True)))
+ # string conversion
+ self.assertEqual(str(Distance(ref, 3, False)), 'Distance((1, 2, 3), 3.0, False)')
+ self.assertEqual(repr(Distance(ref, 3, False)), 'Distance((1, 2, 3), 3.0, False)')
+
+ def test_members(self):
+ self.assertEqual(Distance((1,2,3), 3, False).reference, (1,2,3))
+ self.assertEqual(Distance((3,2,1), 3, False).reference, (3,2,1))
+ self.assertEqual(Distance((1,2,3), 3, False).threshold, 3.0)
+ self.assertEqual(Distance((1,2,3), 53.45, False).threshold, 53.45)
+ self.assertEqual(Distance((1,2,3), 3, False).strict, False)
+ self.assertEqual(Distance((1,2,3), 3, True).strict, True)
+
+
+class TestBounded(unittest.TestCase):
+ def test_essentials(self):
+ # comparison respects type
+ self.assertNotEqual(_Bounded(1234), LessThan(1234))
+ self.assertNotEqual(LessThan(1234), GreaterThan(1234))
+ self.assertNotEqual(GreaterThan(1234), _Bounded(1234))
+ self.assertNotEqual(hash(_Bounded(1234)), hash(LessThan(1234)))
+ self.assertNotEqual(hash(LessThan(1234)), hash(GreaterThan(1234)))
+ self.assertNotEqual(hash(GreaterThan(1234)), hash(_Bounded(1234)))
+
+ for cls in (_Bounded, LessThan, GreaterThan):
+ # comparison
+ self.assertEqual(cls(1234), cls(1234))
+ self.assertEqual(hash(cls(1234)), hash(cls(1234)))
+ # comparison respects threshold
+ self.assertNotEqual(cls(1234), cls(4321))
+ self.assertNotEqual(hash(cls(1234)), hash(cls(4321)))
+ # comparison respects strict
+ self.assertNotEqual(cls(1234, True), cls(1234, False))
+ self.assertNotEqual(hash(cls(1234, True)), hash(cls(1234, False)))
+
+ # string conversion
+ self.assertEqual(str(_Bounded(1234, False)), '_Bounded(1234.0, False)')
+ self.assertEqual(repr(_Bounded(1234, False)), '_Bounded(1234.0, False)')
+ self.assertEqual(str(LessThan(1234, False)), 'LessThan(1234.0, False)')
+ self.assertEqual(repr(LessThan(1234, False)), 'LessThan(1234.0, False)')
+ self.assertEqual(str(GreaterThan(1234, False)), 'GreaterThan(1234.0, False)')
+ self.assertEqual(repr(GreaterThan(1234, False)), 'GreaterThan(1234.0, False)')
+
+ def test_members(self):
+ class Foo(): pass
+ for cls in (_Bounded, LessThan, GreaterThan):
+ # threshold becomes float
+ self.assertEqual(cls(1.234).threshold, 1.234)
+ self.assertEqual(cls(1234).threshold, 1234.0)
+ self.assertEqual(cls('1234').threshold, 1234)
+ self.assertRaises(TypeError, cls, Foo())
+ # strict becomes bool
+ self.assertEqual(cls(1234, True).strict, True)
+ self.assertEqual(cls(1234, False).strict, False)
+ self.assertEqual(cls(1234, Foo()).strict, True)
+
+
+class TestPredicate(unittest.TestCase):
+ def test_essentials(self):
+ # comparison
+ self.assertEqual(Predicate(ns.bse.filesize), Predicate(ns.bse.filesize))
+ self.assertEqual(hash(Predicate(ns.bse.filesize)), hash(Predicate(ns.bse.filesize)))
+ # comparison respects type
+ self.assertNotEqual(Predicate(ns.bse.filesize), PredicateExpression())
+ self.assertNotEqual(hash(Predicate(ns.bse.filesize)), hash(PredicateExpression()))
+ # comparison respects predicate
+ self.assertNotEqual(Predicate(ns.bse.filesize), Predicate(ns.bse.filename))
+ self.assertNotEqual(hash(Predicate(ns.bse.filesize)), hash(Predicate(ns.bse.filename)))
+ # comparison respects reverse
+ self.assertNotEqual(Predicate(ns.bse.filesize, True), Predicate(ns.bse.filesize, False))
+ self.assertNotEqual(hash(Predicate(ns.bse.filesize, True)), hash(Predicate(ns.bse.filesize, False)))
+ # string conversion
+ self.assertEqual(str(Predicate(ns.bse.filesize)), f'Predicate({ns.bse.filesize}, False)')
+ self.assertEqual(str(Predicate(ns.bse.filesize, True)),
+ f'Predicate({ns.bse.filesize}, True)')
+ self.assertEqual(repr(Predicate(ns.bse.filesize)), f'Predicate({ns.bse.filesize}, False)')
+ self.assertEqual(repr(Predicate(ns.bse.filesize, True)),
+ f'Predicate({ns.bse.filesize}, True)')
+
+ def test_members(self):
+ # member returns predicate
+ # predicate must be an URI
+ self.assertEqual(Predicate(ns.bse.filesize).predicate, ns.bse.filesize)
+ self.assertEqual(Predicate(URI('hello_world')).predicate, URI('hello_world'))
+ self.assertRaises(TypeError, Predicate, 1234)
+ self.assertRaises(TypeError, Predicate, FilterExpression())
+ self.assertRaises(TypeError, Predicate, FilterExpression())
+ # reverse becomes a boolean
+ self.assertEqual(Predicate(ns.bse.filesize, True).reverse, True)
+ self.assertEqual(Predicate(ns.bse.filesize, False).reverse, False)
+ self.assertEqual(Predicate(ns.bse.filesize, 'abc').reverse, True)
+
+
+class TestOneOf(unittest.TestCase):
+ def test_essentials(self):
+ expr = {Predicate(ns.bse.filename), Predicate(ns.bse.filesize)}
+ # comparison
+ self.assertEqual(OneOf(expr), OneOf(expr))
+ self.assertEqual(hash(OneOf(expr)), hash(OneOf(expr)))
+ # comparison respects type
+ self.assertNotEqual(OneOf(expr), PredicateExpression())
+ self.assertNotEqual(hash(OneOf(expr)), hash(PredicateExpression()))
+ # comparison respects expression
+ self.assertNotEqual(OneOf(expr), OneOf(Predicate(ns.bse.filename)))
+ self.assertNotEqual(hash(OneOf(expr)), hash(OneOf(Predicate(ns.bse.filename))))
+ # string conversion
+ self.assertEqual(str(OneOf(Predicate(ns.bse.filesize))),
+ f'OneOf({{Predicate({ns.bse.filesize}, False)}})')
+ self.assertEqual(repr(OneOf(Predicate(ns.bse.filesize))),
+ f'OneOf({{Predicate({ns.bse.filesize}, False)}})')
+
+ def test_expression(self):
+ class Foo(): pass
+ # can pass expressions as arguments
+ self.assertSetEqual(OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename)).expr,
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # can pass one expressions as argument
+ self.assertSetEqual(OneOf(Predicate(ns.bse.filesize)).expr,
+ {Predicate(ns.bse.filesize)})
+ # can pass expressions as iterator
+ self.assertSetEqual(OneOf(iter((Predicate(ns.bse.filesize), Predicate(ns.bse.filename)))).expr,
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # can pass expressions as generator
+ def gen():
+ yield Predicate(ns.bse.filesize)
+ yield Predicate(ns.bse.filename)
+ self.assertSetEqual(OneOf(gen()).expr,
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # can pass expressions as list-like
+ self.assertSetEqual(OneOf((Predicate(ns.bse.filesize), Predicate(ns.bse.filename))).expr,
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # can pass one expression as list-like
+ self.assertSetEqual(OneOf([Predicate(ns.bse.filesize)]).expr,
+ {Predicate(ns.bse.filesize)})
+ # must pass expressions
+ self.assertRaises(TypeError, OneOf, Foo(), Foo())
+ self.assertRaises(TypeError, OneOf, [Foo(), Foo()])
+ # must pass at least one expression
+ self.assertRaises(AttributeError, OneOf)
+
+ # iter
+ self.assertSetEqual(set(iter(OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename)))),
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # contains
+ self.assertIn(Predicate(ns.bse.filesize),
+ OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename)))
+ self.assertNotIn(Predicate(ns.bse.tag),
+ OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename)))
+ # len
+ self.assertEqual(len(OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename))), 2)
+ self.assertEqual(len(OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename), Predicate(ns.bse.tag))), 3)
+
+
+ def test_IsIn(self):
+ # cannot pass zero arguments
+ self.assertRaises(AttributeError, IsIn)
+ # can pass expressions as arguments
+ self.assertEqual(IsIn('http://example.com/entity#1234', 'http://example.com/entity#4321'),
+ Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321')))
+ # can pass one expression as argument
+ self.assertEqual(IsIn('http://example.com/entity#1234'),
+ Is('http://example.com/entity#1234'))
+ # can pass expressions as iterator
+ self.assertEqual(IsIn(iter(('http://example.com/entity#1234', 'http://example.com/entity#4321'))),
+ Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321')))
+ # can pass expressions as generator
+ def gen():
+ yield 'http://example.com/entity#1234'
+ yield 'http://example.com/entity#4321'
+ self.assertEqual(IsIn(gen()),
+ Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321')))
+ # can pass expressions as list-like
+ self.assertEqual(IsIn(['http://example.com/entity#1234', 'http://example.com/entity#4321']),
+ Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321')))
+ # can pass one expression as list-like
+ self.assertEqual(IsIn(['http://example.com/entity#1234']),
+ Is('http://example.com/entity#1234'))
+
+
+ def test_IsNotIn(self):
+ # cannot pass zero arguments
+ self.assertRaises(AttributeError, IsNotIn)
+ # can pass expressions as arguments
+ self.assertEqual(IsNotIn('http://example.com/entity#1234', 'http://example.com/entity#4321'),
+ Not(Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321'))))
+ # can pass one expression as argument
+ self.assertEqual(IsNotIn('http://example.com/entity#1234'),
+ Not(Is('http://example.com/entity#1234')))
+ # can pass expressions as iterator
+ self.assertEqual(IsNotIn(iter(('http://example.com/entity#1234', 'http://example.com/entity#4321'))),
+ Not(Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321'))))
+ # can pass expressions as generator
+ def gen():
+ yield 'http://example.com/entity#1234'
+ yield 'http://example.com/entity#4321'
+ self.assertEqual(IsNotIn(gen()),
+ Not(Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321'))))
+ # can pass expressions as list-like
+ self.assertEqual(IsNotIn(['http://example.com/entity#1234', 'http://example.com/entity#4321']),
+ Not(Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321'))))
+ # can pass one expression as list-like
+ self.assertEqual(IsNotIn(['http://example.com/entity#1234']),
+ Not(Is('http://example.com/entity#1234')))
+
+
+ def test_Includes(self):
+ # cannot pass zero arguments
+ self.assertRaises(AttributeError, Includes)
+ # can pass expressions as arguments
+ self.assertEqual(Includes('hello', 'world'),
+ Or(Equals('hello'), Equals('world')))
+ self.assertEqual(Includes('hello', 'world', approx=True),
+ Or(Substring('hello'), Substring('world')))
+ # can pass one expression as argument
+ self.assertEqual(Includes('hello'),
+ Equals('hello'))
+ self.assertEqual(Includes('hello', approx=True),
+ Substring('hello'))
+ # can pass expressions as iterator
+ self.assertEqual(Includes(iter(('hello', 'world'))),
+ Or(Equals('hello'), Equals('world')))
+ self.assertEqual(Includes(iter(('hello', 'world')), approx=True),
+ Or(Substring('hello'), Substring('world')))
+ # can pass expressions as generator
+ def gen():
+ yield 'hello'
+ yield 'world'
+ self.assertEqual(Includes(gen()),
+ Or(Equals('hello'), Equals('world')))
+ self.assertEqual(Includes(gen(), approx=True),
+ Or(Substring('hello'), Substring('world')))
+ # can pass expressions as list-like
+ self.assertEqual(Includes(['hello', 'world']),
+ Or(Equals('hello'), Equals('world')))
+ self.assertEqual(Includes(['hello', 'world'], approx=True),
+ Or(Substring('hello'), Substring('world')))
+ # can pass one expression as list-like
+ self.assertEqual(Includes(['hello']),
+ Equals('hello'))
+ self.assertEqual(Includes(['hello'], approx=True),
+ Substring('hello'))
+
+
+ def test_Excludes(self):
+ # cannot pass zero arguments
+ self.assertRaises(AttributeError, Excludes)
+ # can pass expressions as arguments
+ self.assertEqual(Excludes('hello', 'world'),
+ Not(Or(Equals('hello'), Equals('world'))))
+ self.assertEqual(Excludes('hello', 'world', approx=True),
+ Not(Or(Substring('hello'), Substring('world'))))
+ # can pass one expression as argument
+ self.assertEqual(Excludes('hello'),
+ Not(Equals('hello')))
+ self.assertEqual(Excludes('hello', approx=True),
+ Not(Substring('hello')))
+ # can pass expressions as iterator
+ self.assertEqual(Excludes(iter(('hello', 'world'))),
+ Not(Or(Equals('hello'), Equals('world'))))
+ self.assertEqual(Excludes(iter(('hello', 'world')), approx=True),
+ Not(Or(Substring('hello'), Substring('world'))))
+ # can pass expressions as generator
+ def gen():
+ yield 'hello'
+ yield 'world'
+ self.assertEqual(Excludes(gen()),
+ Not(Or(Equals('hello'), Equals('world'))))
+ self.assertEqual(Excludes(gen(), approx=True),
+ Not(Or(Substring('hello'), Substring('world'))))
+ # can pass expressions as list-like
+ self.assertEqual(Excludes(['hello', 'world']),
+ Not(Or(Equals('hello'), Equals('world'))))
+ self.assertEqual(Excludes(['hello', 'world'], approx=True),
+ Not(Or(Substring('hello'), Substring('world'))))
+ # can pass one expression as list-like
+ self.assertEqual(Excludes(['hello']),
+ Not(Equals('hello')))
+ self.assertEqual(Excludes(['hello'], approx=True),
+ Not(Substring('hello')))
+
+
+ def test_Between(self):
+ # must specify at least one bound
+ self.assertRaises(ValueError, Between, float('inf'), float('inf'))
+ # lower bound must be less than the upper bound
+ self.assertRaises(ValueError, Between, 321, 123)
+ # can set a lower bound only
+ self.assertEqual(Between(123),
+ GreaterThan(123, strict=True))
+ self.assertEqual(Between(123, lo_strict=False),
+ GreaterThan(123, strict=False))
+ # can set an upper bound only
+ self.assertEqual(Between(hi=123),
+ LessThan(123, strict=True))
+ self.assertEqual(Between(hi=123, hi_strict=False),
+ LessThan(123, strict=False))
+ # can set both bounds
+ self.assertEqual(Between(123, 321),
+ And(GreaterThan(123, strict=True), LessThan(321, strict=True)))
+ self.assertEqual(Between(123, 321, False, False),
+ And(GreaterThan(123, strict=False), LessThan(321, strict=False)))
+ # can set identical bounds
+ self.assertRaises(ValueError, Between, 123, 123)
+ self.assertEqual(Between(123, 123, False, False),
+ Equals(123))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/query/test_matcher.py b/test/query/test_matcher.py
new file mode 100644
index 0000000..6b975b2
--- /dev/null
+++ b/test/query/test_matcher.py
@@ -0,0 +1,1177 @@
+
+# imports
+import operator
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.utils import errors
+
+# objects to test
+from bsfs.query.matcher import Any, Filter, Partial, Rest, _set_matcher
+
+
+## code ##
+
+class TestAny(unittest.TestCase):
+ def test_essentials(self):
+ # comparison
+ a = Any()
+ b = Any()
+ self.assertNotEqual(Any(), Any())
+ self.assertNotEqual(hash(Any()), hash(Any()))
+ self.assertNotEqual(a, Any())
+ self.assertNotEqual(hash(a), hash(Any()))
+ self.assertNotEqual(a, b)
+ self.assertNotEqual(hash(a), hash(b))
+ # comparison within sets
+ self.assertEqual(len({Any(), Any(), Any(), Any()}), 4)
+ self.assertEqual(len({Any() for _ in range(1000)}), 1000)
+ # string representation
+ self.assertEqual(str(Any()), 'Any()')
+ self.assertEqual(repr(Any()), 'Any()')
+
+
+class TestRest(unittest.TestCase):
+ def test_essentials(self):
+ expr = ast.filter.Equals('hello')
+ # comparison
+ self.assertEqual(Rest(expr), Rest(expr))
+ self.assertEqual(hash(Rest(expr)), hash(Rest(expr)))
+ # comparison respects type
+ class Foo(): pass
+ self.assertNotEqual(Rest(expr), 1234)
+ self.assertNotEqual(hash(Rest(expr)), hash(1234))
+ self.assertNotEqual(Rest(expr), Foo())
+ self.assertNotEqual(hash(Rest(expr)), hash(Foo()))
+ # comparison respects expr
+ self.assertNotEqual(Rest(expr), Rest(ast.filter.Equals('world')))
+ self.assertNotEqual(hash(Rest(expr)), hash(Rest(ast.filter.Equals('world'))))
+ # default constructor -> Any -> Not equal
+ self.assertNotEqual(Rest(), Rest())
+ self.assertNotEqual(hash(Rest()), hash(Rest()))
+ # string representation
+ self.assertEqual(str(Rest()), 'Rest(Any())')
+ self.assertEqual(str(Rest(expr)), 'Rest(Equals(hello))')
+ self.assertEqual(repr(Rest()), 'Rest(Any())')
+ self.assertEqual(repr(Rest(expr)), 'Rest(Equals(hello))')
+
+
+
+class TestPartial(unittest.TestCase):
+ def test_match(self):
+ p0 = Partial(ast.filter.LessThan)
+ p1 = Partial(ast.filter.LessThan, threshold=3)
+ p2 = Partial(ast.filter.LessThan, strict=False)
+ p3 = Partial(ast.filter.LessThan, threshold=3, strict=False)
+ # match respects name
+ self.assertTrue(p0.match('foo', None))
+ self.assertTrue(p1.match('foo', None))
+ self.assertTrue(p2.match('foo', None))
+ self.assertTrue(p3.match('foo', None))
+ # match respects correct value
+ self.assertTrue(p0.match('threshold', 3))
+ self.assertTrue(p1.match('threshold', 3))
+ self.assertTrue(p2.match('threshold', 3))
+ self.assertTrue(p3.match('threshold', 3))
+ self.assertTrue(p0.match('strict', False))
+ self.assertTrue(p1.match('strict', False))
+ self.assertTrue(p2.match('strict', False))
+ self.assertTrue(p3.match('strict', False))
+ # match respects incorrect value
+ self.assertTrue(p0.match('threshold', 5))
+ self.assertFalse(p1.match('threshold', 5))
+ self.assertTrue(p2.match('threshold', 5))
+ self.assertFalse(p3.match('threshold', 5))
+ self.assertTrue(p0.match('strict', True))
+ self.assertTrue(p1.match('strict', True))
+ self.assertFalse(p2.match('strict', True))
+ self.assertFalse(p3.match('strict', True))
+
+ def test_members(self):
+ # node returns expression
+ self.assertEqual(Partial(ast.filter.Equals).node, ast.filter.Equals)
+ self.assertEqual(Partial(ast.filter.LessThan).node, ast.filter.LessThan)
+ # kwargs returns arguments
+ self.assertDictEqual(Partial(ast.filter.Equals, value='hello').kwargs,
+ {'value': 'hello'})
+ self.assertDictEqual(Partial(ast.filter.LessThan, threshold=3, strict=False).kwargs,
+ {'threshold': 3, 'strict': False})
+ # Partial does not check about kwargs
+ self.assertDictEqual(Partial(ast.filter.LessThan, value='hello').kwargs,
+ {'value': 'hello'})
+ self.assertDictEqual(Partial(ast.filter.Equals, threshold=3, strict=False).kwargs,
+ {'threshold': 3, 'strict': False})
+
+ def test_essentials(self):
+ # comparison respects type
+ class Foo(): pass
+ self.assertNotEqual(Partial(ast.filter.Equals), 1234)
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(1234))
+ self.assertNotEqual(Partial(ast.filter.Equals), Foo())
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(Foo()))
+ self.assertNotEqual(Partial(ast.filter.Equals), ast.filter.Equals)
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(ast.filter.Equals))
+ self.assertNotEqual(Partial(ast.filter.Equals), ast.filter.Equals('hello'))
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(ast.filter.Equals('hello')))
+ # comparison respects node
+ self.assertEqual(Partial(ast.filter.Equals), Partial(ast.filter.Equals))
+ self.assertEqual(hash(Partial(ast.filter.Equals)), hash(Partial(ast.filter.Equals)))
+ self.assertEqual(Partial(ast.filter.LessThan), Partial(ast.filter.LessThan))
+ self.assertEqual(hash(Partial(ast.filter.LessThan)), hash(Partial(ast.filter.LessThan)))
+ self.assertNotEqual(Partial(ast.filter.Equals), Partial(ast.filter.LessThan))
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(Partial(ast.filter.LessThan)))
+ # comparison respects kwargs
+ self.assertEqual(
+ Partial(ast.filter.Equals, value='hello'),
+ Partial(ast.filter.Equals, value='hello'))
+ self.assertEqual(
+ hash(Partial(ast.filter.Equals, value='hello')),
+ hash(Partial(ast.filter.Equals, value='hello')))
+ self.assertEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3, strict=False))
+ self.assertEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)))
+ self.assertNotEqual(
+ Partial(ast.filter.Equals, value='hello'),
+ Partial(ast.filter.Equals))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.Equals, value='hello')),
+ hash(Partial(ast.filter.Equals)))
+ self.assertNotEqual(
+ Partial(ast.filter.Equals, value='hello'),
+ Partial(ast.filter.Equals, value='world'))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.Equals, value='hello')),
+ hash(Partial(ast.filter.Equals, value='world')))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan)))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=5))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, threshold=5)))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, strict=False))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, strict=False)))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3, strict=True))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=True)))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=5, strict=False))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, threshold=5, strict=False)))
+ # string representation
+ self.assertEqual(str(Partial(ast.filter.Equals)), 'Partial(Equals, {})')
+ self.assertEqual(repr(Partial(ast.filter.Equals)), 'Partial(Equals, {})')
+ self.assertEqual(str(Partial(ast.filter.LessThan)), 'Partial(LessThan, {})')
+ self.assertEqual(repr(Partial(ast.filter.LessThan)), 'Partial(LessThan, {})')
+ self.assertEqual(str(Partial(ast.filter.Equals, value='hello')), "Partial(Equals, {'value': 'hello'})")
+ self.assertEqual(repr(Partial(ast.filter.Equals, value='hello')), "Partial(Equals, {'value': 'hello'})")
+ self.assertEqual(str(Partial(ast.filter.LessThan, threshold=3)), "Partial(LessThan, {'threshold': 3})")
+ self.assertEqual(repr(Partial(ast.filter.LessThan, threshold=3)), "Partial(LessThan, {'threshold': 3})")
+ self.assertEqual(str(Partial(ast.filter.LessThan, strict=False)), "Partial(LessThan, {'strict': False})")
+ self.assertEqual(repr(Partial(ast.filter.LessThan, strict=False)), "Partial(LessThan, {'strict': False})")
+ self.assertEqual(str(Partial(ast.filter.LessThan, threshold=3, strict=False)), "Partial(LessThan, {'threshold': 3, 'strict': False})")
+ self.assertEqual(repr(Partial(ast.filter.LessThan, threshold=3, strict=False)), "Partial(LessThan, {'threshold': 3, 'strict': False})")
+
+
+class TestSetMatcher(unittest.TestCase):
+ def test_set_matcher(self):
+ # setup
+ A = ast.filter.Equals('A')
+ B = ast.filter.Equals('B')
+ C = ast.filter.Equals('C')
+ D = ast.filter.Equals('D')
+ matcher = Filter()
+
+ # identical sets match
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, C),
+ matcher._parse_filter_expression,
+ ))
+
+ # order is irrelevant
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(B, C, A),
+ matcher._parse_filter_expression,
+ ))
+
+ # all reference items must be present
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B, C),
+ matcher._parse_filter_expression,
+ ))
+
+ # all reference items must have a match
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(D, B, C),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, D, C),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, D),
+ matcher._parse_filter_expression,
+ ))
+
+ # Any matches every item
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), B, C),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Any(), C),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, D),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+
+ # there can be multiple Any's
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Any(), Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), Any(), C),
+ matcher._parse_filter_expression,
+ ))
+
+ # Any covers exactly one element
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, D),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C, D),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+
+ # each Any covers exactly one element
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), Any(), Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(Any(), Any(), Any()),
+ matcher._parse_filter_expression,
+ ))
+
+ # Rest captures remainder
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C, D),
+ ast.filter.And(A, B, Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # remainder matches the empty set
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B, Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # Rest does not absolve other refernce items from having a match
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, C, D),
+ ast.filter.And(A, B, Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # Rest can be combined with Any ...
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, C, D),
+ ast.filter.And(A, Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, C, D),
+ ast.filter.And(A, Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # ... explicit items still need to match
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, C, D),
+ ast.filter.And(B, Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # ... Any still determines minimum element count
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, Any(), Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # Rest cannot be repeated ...
+ self.assertRaises(errors.BackendError, _set_matcher,
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Rest(), Rest(ast.filter.Equals('hello'))),
+ matcher._parse_filter_expression,
+ )
+ # ... unless they are identical
+ self.assertRaises(errors.BackendError, _set_matcher,
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Rest(), Rest()), # Any instances are different!
+ matcher._parse_filter_expression,
+ )
+ # ... unless they are identical
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(C), Rest(C)),
+ matcher._parse_filter_expression,
+ ))
+ # Rest can mandate a specific expression
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(C)),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(D)),
+ matcher._parse_filter_expression,
+ ))
+ # Rest can mandate a partial expression
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(Partial(ast.filter.Equals))),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Rest(Partial(ast.filter.Equals))),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(Partial(ast.filter.Substring))),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(Partial(ast.filter.Equals, value='D'))),
+ matcher._parse_filter_expression,
+ ))
+ # Rest can be the only expression
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Rest(Partial(ast.filter.Equals))),
+ matcher._parse_filter_expression,
+ ))
+ # Rest's expression defaults to Any
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Rest()),
+ matcher._parse_filter_expression,
+ ))
+
+
+class TestFilter(unittest.TestCase):
+ def setUp(self):
+ self.match = Filter()
+
+ def test_call(self):
+ # query must be a filter expression
+ self.assertRaises(errors.BackendError, self.match, 1234, Any())
+ self.assertRaises(errors.BackendError, self.match, ast.filter.Predicate(ns.bse.filename), Any())
+ # reference must be a filter expression
+ self.assertRaises(errors.BackendError, self.match, ast.filter.Equals('hello'), 1234)
+ self.assertRaises(errors.BackendError, self.match, ast.filter.Equals('hello'), ast.filter.Predicate(ns.bse.filename))
+ # reference can be Any or Partial
+ self.assertTrue(self.match(
+ ast.filter.Equals('hello'),
+ Any(),
+ ))
+ self.assertTrue(self.match(
+ ast.filter.Equals('hello'),
+ Partial(ast.filter.Equals),
+ ))
+ # call parses expression
+ self.assertTrue(self.match(
+ # query
+ ast.filter.And(
+ ast.filter.Any(ns.bse.tag,
+ ast.filter.All(ns.bse.label,
+ ast.filter.Or(
+ ast.filter.Equals('hello'),
+ ast.filter.Equals('world'),
+ ast.filter.StartsWith('foo'),
+ ast.filter.EndsWith('bar'),
+ )
+ )
+ ),
+ ast.filter.Any(ns.bse.iso,
+ ast.filter.And(
+ ast.filter.GreaterThan(100, strict=True),
+ ast.filter.LessThan(200, strict=False),
+ )
+ ),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.featureA, ns.bse.featureB),
+ ast.filter.Distance([1,2,3], 1)
+ ),
+ ),
+ # reference
+ ast.filter.And(
+ ast.filter.Any(Any(),
+ ast.filter.All(Partial(ast.filter.Predicate, reverse=False),
+ ast.filter.Or(
+ Partial(ast.filter.StartsWith),
+ ast.filter.EndsWith('bar'),
+ Rest(Partial(ast.filter.Equals)),
+ )
+ )
+ ),
+ ast.filter.Any(ns.bse.iso,
+ ast.filter.And(
+ Partial(ast.filter.GreaterThan, strict=True),
+ Any(),
+ Rest(),
+ )
+ ),
+ ast.filter.Any(ast.filter.OneOf(Rest()),
+ Partial(ast.filter.Distance)
+ ),
+ ),
+ ))
+ self.assertFalse(self.match(
+ # query
+ ast.filter.Any(ns.bse.tag,
+ ast.filter.And(
+ ast.filter.Any(ns.bse.label, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.collection, ast.filter.Is('http://example.com/col#123')),
+ ast.filter.Not(ast.filter.Has(ns.bse.label)),
+ )
+ ),
+ # reference
+ ast.filter.Any(ns.bse.tag,
+ ast.filter.And(
+ Any(),
+ ast.filter.Any(Partial(ast.filter.Predicate, reverse=True), # reverse mismatch
+ Partial(ast.filter.Is)),
+ ast.filter.Not(ast.filter.Has(Any(), Any())),
+ )
+ )
+ ))
+
+ def test_parse_filter_expression(self):
+ # Any matches every filter expression
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Not(ast.filter.FilterExpression()), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Has(ns.bse.filename), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Distance([1,2,3], 1.0), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.And(ast.filter.Equals('hello')), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Or(ast.filter.Equals('hello')), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Equals('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Substring('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.StartsWith('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.EndsWith('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Is('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.LessThan(3), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.GreaterThan(3), Any()))
+ # Any matches invalid filter expressions
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.FilterExpression(), Any()))
+ # node must be an appropriate filter expression
+ self.assertRaises(errors.BackendError, self.match._parse_filter_expression,
+ ast.filter.FilterExpression(), ast.filter.FilterExpression())
+ self.assertRaises(errors.BackendError, self.match._parse_filter_expression,
+ 1234, ast.filter.FilterExpression())
+
+ def test_parse_predicate_expression(self):
+ # Any matches every predicate expression
+ self.assertTrue(self.match._parse_predicate_expression(
+ ast.filter.Predicate(ns.bse.filename), Any()))
+ self.assertTrue(self.match._parse_predicate_expression(
+ ast.filter.OneOf(ns.bse.filename), Any()))
+ # Any matches invalid predicate expression
+ self.assertTrue(self.match._parse_predicate_expression(
+ ast.filter.FilterExpression(), Any()))
+ # node must be an appropriate predicate expression
+ self.assertRaises(errors.BackendError, self.match._parse_predicate_expression,
+ ast.filter.PredicateExpression(), ast.filter.PredicateExpression())
+ self.assertRaises(errors.BackendError, self.match._parse_predicate_expression,
+ 1234, ast.filter.PredicateExpression())
+
+ def test_predicate(self):
+ # identical expressions match
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ))
+ # _predicate respects type
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ast.filter.FilterExpression(),
+ ))
+ # _predicate respects predicate
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ast.filter.Predicate(ns.bse.filesize, reverse=False),
+ ))
+ # _predicate respects reverse
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ast.filter.Predicate(ns.bse.filename, reverse=True),
+ ))
+ # Partial requires ast.filter.Predicate
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Equals),
+ ))
+ # predicate and reverse can be specified
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filename, reverse=False),
+ ))
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filesize, reverse=False),
+ ))
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filename, reverse=True),
+ ))
+ # predicate can remain unspecified
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, reverse=False),
+ ))
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filesize, reverse=False),
+ Partial(ast.filter.Predicate, reverse=False),
+ ))
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filesize, reverse=False),
+ Partial(ast.filter.Predicate, reverse=True),
+ ))
+ # reverse can remain unspecified
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filename),
+ ))
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=True),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filename),
+ ))
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filesize),
+ ))
+
+ def test_one_of(self):
+ A = ast.filter.Predicate(ns.bse.filename)
+ B = ast.filter.Predicate(ns.bse.filesize)
+ C = ast.filter.Predicate(ns.bse.filename, reverse=True)
+ # identical expressions match
+ self.assertTrue(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A, B),
+ ))
+ # _one_of respects type
+ self.assertFalse(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.Predicate(ns.bse.filesize, reverse=True),
+ ))
+ # _one_of respects child expressions
+ self.assertFalse(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A, C),
+ ))
+ self.assertFalse(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A),
+ ))
+ self.assertFalse(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A, B, C),
+ ))
+ self.assertTrue(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(B, A),
+ ))
+ self.assertTrue(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A, Any()),
+ ))
+ self.assertTrue(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(B, Rest()),
+ ))
+
+ def test_branch(self):
+ # identical expressions match
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ))
+ # _agg respects type
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Equals('hello'),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Equals('hello'),
+ ))
+ # _agg respects predicate expression
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.filename), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ast.filter.Predicate(ns.bse.filename), ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filesize, ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.filename), ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ast.filter.OneOf(ns.bse.filename), ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.filename, reverse=True), ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ast.filter.Predicate(ns.bse.filename, reverse=True), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(Any(), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(Any(), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(Partial(ast.filter.Predicate), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(Partial(ast.filter.Predicate), ast.filter.Equals('hello')),
+ ))
+ # _agg respects filter expression
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, ast.filter.Substring('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, ast.filter.Substring('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, ast.filter.Any(Any(), Any())),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, ast.filter.All(Any(), Any())),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, Any()),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, Any()),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, Partial(ast.filter.Equals)),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, Partial(ast.filter.Equals)),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, Partial(ast.filter.Equals, value='world')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, Partial(ast.filter.Equals, value='world')),
+ ))
+
+ def test_agg(self):
+ A = ast.filter.Equals('hello')
+ B = ast.filter.Equals('world')
+ C = ast.filter.Equals('foobar')
+ # identical expressions match
+ self.assertTrue(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A, B),
+ ))
+ # _agg respects type
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.Or(A, B),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.And(A, B),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.Equals('hello'),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Equals('hello'),
+ ))
+ # _agg respects child expressions
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A, ast.filter.Equals('bar')),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A, ast.filter.Equals('bar')),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B, C),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A, B, C),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(B, A),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(B, A),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A, Any()),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A, Any()),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(B, Rest()),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(B, Rest()),
+ ))
+
+ def test_not(self):
+ # identical expressions match
+ self.assertTrue(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ))
+ # _not respects type
+ self.assertFalse(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Equals('hello'),
+ ))
+ # _not respects child expression
+ self.assertFalse(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Not(ast.filter.Equals('world')),
+ ))
+ self.assertFalse(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Not(ast.filter.Substring('hello')),
+ ))
+ self.assertTrue(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Not(Any()),
+ ))
+
+ def test_has(self):
+ # identical expressions match
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize),
+ ast.filter.Has(ns.bse.filesize),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ))
+ # _has respects type
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize),
+ ast.filter.Equals('hello'),
+ ))
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize),
+ ast.filter.Equals('hello'),
+ ))
+ # _has respects predicate
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.iso, ast.filter.LessThan(3)),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(Any(), ast.filter.LessThan(3)),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(Partial(ast.filter.Predicate), ast.filter.LessThan(3)),
+ ))
+ # _has respects count
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, ast.filter.GreaterThan(3)),
+ ))
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(5)),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, Any()),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, Partial(ast.filter.LessThan)),
+ ))
+
+ def test_distance(self):
+ # identical expressions match
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Distance([1,2,3], 5, True),
+ ))
+ # _distance respects type
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Equals('hello'),
+ ))
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Equals),
+ ))
+ # _distance respects reference value
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Distance([3,2,1], 5, True),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, threshold=5, strict=True),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5, strict=True),
+ ))
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[3,2,1], threshold=5, strict=True),
+ ))
+ # _distance respects threshold
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Distance([1,2,3], 8, True),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], strict=True),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5, strict=True),
+ ))
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=8, strict=True),
+ ))
+ # _distance respects strict
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Distance([1,2,3], 5, False),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5, strict=True),
+ ))
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5, strict=False),
+ ))
+
+ def test_value(self):
+ # identical expressions match
+ self.assertTrue(self.match._value(ast.filter.Equals('hello'), ast.filter.Equals('hello')))
+ self.assertTrue(self.match._value(ast.filter.Substring('hello'), ast.filter.Substring('hello')))
+ self.assertTrue(self.match._value(ast.filter.StartsWith('hello'), ast.filter.StartsWith('hello')))
+ self.assertTrue(self.match._value(ast.filter.EndsWith('hello'), ast.filter.EndsWith('hello')))
+ self.assertTrue(self.match._value(ast.filter.Is('hello'), ast.filter.Is('hello')))
+ # _value respects type
+ self.assertFalse(self.match._value(ast.filter.Equals('hello'), ast.filter.Is('hello')))
+ self.assertFalse(self.match._value(ast.filter.Substring('hello'), ast.filter.Is('hello')))
+ self.assertFalse(self.match._value(ast.filter.StartsWith('hello'), ast.filter.Is('hello')))
+ self.assertFalse(self.match._value(ast.filter.EndsWith('hello'), ast.filter.Is('hello')))
+ self.assertFalse(self.match._value(ast.filter.Is('hello'), ast.filter.Equals('hello')))
+ # _value respects value
+ self.assertFalse(self.match._value(ast.filter.Equals('hello'), ast.filter.Equals('world')))
+ self.assertFalse(self.match._value(ast.filter.Substring('hello'), ast.filter.Substring('world')))
+ self.assertFalse(self.match._value(ast.filter.StartsWith('hello'), ast.filter.StartsWith('world')))
+ self.assertFalse(self.match._value(ast.filter.EndsWith('hello'), ast.filter.EndsWith('world')))
+ self.assertFalse(self.match._value(ast.filter.Is('hello'), ast.filter.Is('world')))
+ # Partial requires correct type
+ self.assertFalse(self.match._value(ast.filter.Equals('hello'), Partial(ast.filter.Is)))
+ self.assertFalse(self.match._value(ast.filter.Substring('hello'), Partial(ast.filter.Is)))
+ self.assertFalse(self.match._value(ast.filter.StartsWith('hello'), Partial(ast.filter.Is)))
+ self.assertFalse(self.match._value(ast.filter.EndsWith('hello'), Partial(ast.filter.Is)))
+ self.assertFalse(self.match._value(ast.filter.Is('hello'), Partial(ast.filter.Equals)))
+ # value can be specified
+ self.assertTrue(self.match._value(ast.filter.Equals('hello'), Partial(ast.filter.Equals, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.Equals('hello'), Partial(ast.filter.Equals, value='world')))
+ self.assertTrue(self.match._value(ast.filter.Substring('hello'), Partial(ast.filter.Substring, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.Substring('hello'), Partial(ast.filter.Substring, value='world')))
+ self.assertTrue(self.match._value(ast.filter.StartsWith('hello'), Partial(ast.filter.StartsWith, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.StartsWith('hello'), Partial(ast.filter.StartsWith, value='world')))
+ self.assertTrue(self.match._value(ast.filter.EndsWith('hello'), Partial(ast.filter.EndsWith, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.EndsWith('hello'), Partial(ast.filter.EndsWith, value='world')))
+ self.assertTrue(self.match._value(ast.filter.Is('hello'), Partial(ast.filter.Is, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.Is('hello'), Partial(ast.filter.Is, value='world')))
+ # value can remain unspecified
+ self.assertTrue(self.match._value(ast.filter.Equals('hello'), Partial(ast.filter.Equals)))
+ self.assertTrue(self.match._value(ast.filter.Substring('hello'), Partial(ast.filter.Substring)))
+ self.assertTrue(self.match._value(ast.filter.StartsWith('hello'), Partial(ast.filter.StartsWith)))
+ self.assertTrue(self.match._value(ast.filter.EndsWith('hello'), Partial(ast.filter.EndsWith)))
+ self.assertTrue(self.match._value(ast.filter.Is('hello'), Partial(ast.filter.Is)))
+
+ def test_bounded(self):
+ # identical expressions match
+ self.assertTrue(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ ast.filter.LessThan(threshold=3, strict=False),
+ ))
+ self.assertTrue(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ))
+ # _bounded respects type
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ast.filter.LessThan(threshold=3, strict=False),
+ ))
+ # _bounded respects threshold
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ ast.filter.LessThan(threshold=4, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ast.filter.GreaterThan(threshold=4, strict=False),
+ ))
+ # _bounded respects strict
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ ast.filter.LessThan(threshold=3, strict=True),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ast.filter.GreaterThan(threshold=3, strict=True),
+ ))
+ # Partial requires correct type
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan),
+ ))
+ # threshold and strict can be specified
+ self.assertTrue(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ ))
+ self.assertTrue(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=3, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=4, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=4, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3, strict=True),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=3, strict=True),
+ ))
+ # threshold can remain unspecified
+ self.assertTrue(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, strict=False),
+ ))
+ self.assertTrue(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, strict=True),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, strict=True),
+ ))
+ # strict can remain unspecified
+ self.assertTrue(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3),
+ ))
+ self.assertTrue(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=3),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=4),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=4),
+ ))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/query/test_validator.py b/test/query/test_validator.py
new file mode 100644
index 0000000..418463e
--- /dev/null
+++ b/test/query/test_validator.py
@@ -0,0 +1,505 @@
+
+# imports
+import unittest
+
+# bsfs imports
+from bsfs import schema as _schema
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.utils import errors
+
+# objects to test
+from bsfs.query.validator import Filter, Fetch
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+
+class TestFilter(unittest.TestCase):
+ def setUp(self):
+ self.schema = _schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:URI rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ <https://schema.bsfs.io/core/Literal/Array/Feature> rdfs:subClassOf bsl:Array .
+ xsd:integer rdfs:subClassOf bsl:Number .
+
+ bsfs:Colors rdfs:subClassOf <https://schema.bsfs.io/core/Literal/Array/Feature> ;
+ bsfs:dimension "5"^^xsd:integer ;
+ bsfs:dtype bsfs:f32 .
+
+ bse:color rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Colors ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ 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:label rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range xsd:string ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:buddy rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ self.validate = Filter(self.schema)
+
+ def test_call(self): # tests validate implicitly
+ # root_type must be a _schema.Node
+ self.assertRaises(TypeError, self.validate, 1234, None)
+ self.assertRaises(TypeError, self.validate, '1234', None)
+ self.assertRaises(TypeError, self.validate, self.schema.literal(ns.bsfs.URI), None)
+ # root_type must exist in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Image), None)
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity).child(ns.bsfs.Image), None)
+ # valid query returns true
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/symbol#1234'),
+ ast.filter.All(ns.bse.comment, ast.filter.StartsWith('foo')),
+ ast.filter.And(
+ ast.filter.Has(ns.bse.comment, ast.filter.Or(
+ ast.filter.GreaterThan(5),
+ ast.filter.LessThan(1),
+ )
+ ),
+ ast.filter.Not(ast.filter.Any(ns.bse.comment,
+ ast.filter.Not(ast.filter.Equals('hello world')))),
+ ast.filter.Any(ns.bse.color, ast.filter.Distance([1,2,3,4,5], 3)),
+ )))))
+ # invalid paths raise consistency error
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy),
+ ast.filter.Or(
+ ast.filter.All(ns.bse.comment, ast.filter.Equals('hello world')),
+ ast.filter.All(ns.bse.label, ast.filter.Equals('hello world')), # domain mismatch
+ )))
+
+ def test_routing(self):
+ self.assertRaises(errors.BackendError, self.validate._parse_filter_expression, ast.filter.FilterExpression(), self.schema.node(ns.bsfs.Node))
+ self.assertRaises(errors.BackendError, self.validate._parse_predicate_expression, ast.filter.PredicateExpression())
+
+ def test_predicate(self):
+ # predicate must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._predicate, ast.filter.Predicate(ns.bse.invalid))
+ # predicate must have a range
+ self.assertRaises(errors.BackendError, self.validate._predicate, ast.filter.Predicate(ns.bsfs.Predicate))
+ # predicate returns domain and range
+ self.assertEqual(self.validate._predicate(ast.filter.Predicate(ns.bse.tag)),
+ (self.schema.node(ns.bsfs.Entity), self.schema.node(ns.bsfs.Tag)))
+ # reverse is applied
+ self.assertEqual(self.validate._predicate(ast.filter.Predicate(ns.bse.tag, reverse=True)),
+ (self.schema.node(ns.bsfs.Tag), self.schema.node(ns.bsfs.Entity)))
+
+ def test_one_of(self):
+ # domains must both be nodes or literals
+ self.assertRaises(errors.ConsistencyError, self.validate._one_of, ast.filter.OneOf(ns.bse.tag, ast.filter.Predicate(ns.bse.label, reverse=True)))
+ # domains must be related
+ self.assertRaises(errors.ConsistencyError, self.validate._one_of, ast.filter.OneOf(ns.bse.tag, ns.bse.label))
+ # ranges must both be nodes or literals
+ self.assertRaises(errors.ConsistencyError, self.validate._one_of, ast.filter.OneOf(ns.bse.tag, ns.bse.comment))
+ # ranges must be related
+ self.assertRaises(errors.ConsistencyError, self.validate._one_of, ast.filter.OneOf(ns.bse.tag, ast.filter.Predicate(ns.bse.buddy, reverse=True)))
+ # one_of returns most specific domain
+ self.assertEqual(self.validate._one_of(ast.filter.OneOf(ns.bse.comment, ns.bse.label)),
+ (self.schema.node(ns.bsfs.Tag), self.schema.literal(ns.xsd.string)))
+ # one_of returns the most generic range
+ self.assertEqual(self.validate._one_of(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy)),
+ (self.schema.node(ns.bsfs.Entity), self.schema.node(ns.bsfs.Node)))
+
+ def test_branch(self):
+ # type must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal), None)
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), None)
+ # predicate is verified
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bsfs.Invalid, ast.filter.Is('http://example.com/entity#1234')))
+ # predicate must match the domain
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/tag#1234')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/tag#1234')))
+ # child expression must be valid
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.tag, ast.filter.Equals('hello world')))
+ # branch accepts valid expressions
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/entity#1234'))))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.All(ns.bse.tag, ast.filter.Is('http://example.com/entity#1234'))))
+
+ def test_agg(self):
+ # agg evaluates child expressions
+ self.assertRaises(errors.ConsistencyError, self.validate._agg, self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Equals('hello world')))
+ self.assertRaises(errors.ConsistencyError, self.validate._agg, self.schema.literal(ns.xsd.string),
+ ast.filter.And(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Equals('hello world')))
+ self.assertRaises(errors.ConsistencyError, self.validate._agg, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Equals('hello world')))
+ self.assertRaises(errors.ConsistencyError, self.validate._agg, self.schema.literal(ns.xsd.string),
+ ast.filter.Or(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Equals('hello world')))
+ # agg works on nodes
+ self.assertIsNone(self.validate._agg(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Is('http://example.com/entity#4321'))))
+ self.assertIsNone(self.validate._agg(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Is('http://example.com/entity#4321'))))
+ # agg works on literals
+ self.assertIsNone(self.validate._agg(self.schema.literal(ns.xsd.string),
+ ast.filter.And(ast.filter.Equals('foobar'), ast.filter.Equals('hello world'))))
+ self.assertIsNone(self.validate._agg(self.schema.literal(ns.xsd.string),
+ ast.filter.Or(ast.filter.Equals('foobar'), ast.filter.Equals('hello world'))))
+
+ def test_not(self):
+ # not evaluates child expressions
+ self.assertRaises(errors.ConsistencyError, self.validate._not, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(ast.filter.Equals('hello world')))
+ self.assertRaises(errors.ConsistencyError, self.validate._not, self.schema.literal(ns.xsd.string),
+ ast.filter.Not(ast.filter.Is('http://example.com/entity#1234')))
+ # not works on nodes
+ self.assertIsNone(self.validate._not(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(ast.filter.Is('http://example.com/entity#1234'))))
+ # not works on literals
+ self.assertIsNone(self.validate._not(self.schema.literal(ns.xsd.string),
+ ast.filter.Not(ast.filter.Equals('hello world'))))
+
+ def test_has(self):
+ # type must be node
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.literal(ns.bsfs.Literal),
+ ast.filter.Has(ns.bse.tag))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.filter.Has(ns.bse.tag))
+ # has checks predicate
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.invalid))
+ # predicate must match domain
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Tag),
+ ast.filter.Has(ns.bse.tag))
+ # has checks count expression
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.tag, ast.filter.Is('http://example.com/entity#1234')))
+ # has accepts correct expressions
+ self.assertIsNone(self.validate._has(self.schema.node(ns.bsfs.Entity), ast.filter.Has(ns.bse.tag, ast.filter.GreaterThan(5))))
+
+ def test_is(self):
+ # type must be node
+ self.assertRaises(errors.ConsistencyError, self.validate._is, self.schema.literal(ns.bsfs.Literal),
+ ast.filter.Is('http://example.com/foo'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._is, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.filter.Is('http://example.com/foo'))
+ # is accepts correct expressions
+ self.assertIsNone(self.validate._is(self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/entity#1234')))
+
+ def test_value(self):
+ # type must be literal
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
+ ast.filter.Equals('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
+ ast.filter.Substring('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
+ ast.filter.StartsWith('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
+ ast.filter.EndsWith('hello world'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.Equals('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.Substring('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.StartsWith('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.EndsWith('hello world'))
+ # value accepts correct expressions
+ self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.Equals('hello world')))
+ self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.Substring('hello world')))
+ self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.StartsWith('hello world')))
+ self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.EndsWith('hello world')))
+
+ def test_bounded(self):
+ # type must be literal
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.node(ns.bsfs.Node),
+ ast.filter.GreaterThan(0))
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.node(ns.bsfs.Node),
+ ast.filter.LessThan(0))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.GreaterThan(0))
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.LessThan(0))
+ # type must be a number
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.xsd.string),
+ ast.filter.LessThan(0))
+ # bounded accepts correct expressions
+ self.assertIsNone(self.validate._bounded(self.schema.literal(ns.xsd.integer), ast.filter.LessThan(0)))
+ self.assertIsNone(self.validate._bounded(self.schema.literal(ns.xsd.integer), ast.filter.GreaterThan(0)))
+
+ def test_distance(self):
+ # type must be a literal
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.node(ns.bsfs.Node),
+ ast.filter.Distance([1,2,3], 1, False))
+ # type must be a feature
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsl.Array),
+ ast.filter.Distance([1,2,3], 1, False))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsl.Array.Feature).child(ns.bsfs.Invalid),
+ ast.filter.Distance([1,2,3], 1, False))
+ # FIXME: reference must be a numpy array
+ # reference must have the correct dimension
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsfs.Colors),
+ ast.filter.Distance([1,2,3], 1, False))
+ # FIXME: reference must have the correct dtype
+ # distance accepts correct expressions
+ self.assertIsNone(self.validate._distance(self.schema.literal(ns.bsfs.Colors), ast.filter.Distance([1,2,3,4,5], 1, False)))
+
+
+class TestFetch(unittest.TestCase):
+ def setUp(self):
+ self.schema = _schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:string .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag .
+
+ bse:label rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range xsd:string .
+
+ ''')
+ self.validate = Fetch(self.schema)
+
+ def test_call(self): # tests validate implicitly
+ # call accepts correct expressions
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bse.label, 'value'))))
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this'))))
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.This('this')))
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.This('this'), ast.fetch.Node(ns.bse.tag, 'node'), ast.fetch.Value(ns.bse.filename, 'value'))))
+ # type must be a Node
+ self.assertRaises(TypeError, self.validate, 1234, ast.fetch.This('this'))
+ self.assertRaises(TypeError, self.validate, 'foobar', ast.fetch.This('this'))
+ self.assertRaises(TypeError, self.validate, self.schema.literal(ns.bsfs.Literal), ast.fetch.This('this'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.FetchExpression())
+ # expression must be a fetch expression
+ self.assertRaises(TypeError, self.validate, self.schema.node(ns.bsfs.Entity), 1234)
+ self.assertRaises(TypeError, self.validate, self.schema.node(ns.bsfs.Entity), 'hello')
+ self.assertRaises(TypeError, self.validate, self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression())
+ # expression must be valid
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Node(ns.bse.label, 'node')))
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.tag, 'value'))
+
+ def test_routing(self):
+ # Node passes _branch, _named, and _node checks
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Node(ns.bse.tag, 'node')) # fails in _branch
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, '')) # fails in _named
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.label, 'node')) # fails in _node
+ # Value passes _branch, _named, and _value checks
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Value(ns.bse.label, 'value')) # fails in _branch
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, '')) # fails in _named
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.tag, 'value')) # fails in _value
+ # Fetch passes _branch and _fetch checks
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this'))) # fails in _branch
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.filename, ast.fetch.This('this'))) # fails in _fetch
+ # invalid expressions cannot be parsed
+ type_ = self.schema.node(ns.bsfs.Node)
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, type_,
+ ast.filter.FilterExpression())
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, type_,
+ 1234)
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, type_,
+ 'hello world')
+
+ def test_all(self):
+ # all accepts correct expressions
+ self.assertIsNone(self.validate._all(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.filename, 'value'), ast.fetch.Node(ns.bse.tag, 'node'))))
+ # child expressions must be valid
+ self.assertRaises(errors.ConsistencyError, self.validate._all, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.tag, 'value')))
+ self.assertRaises(errors.ConsistencyError, self.validate._all, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.filename, 'value'), ast.fetch.Node(ns.bse.filename, 'node')))
+ self.assertRaises(errors.ConsistencyError, self.validate._all, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.tag, 'value'), ast.fetch.Node(ns.bse.tag, 'node')))
+ self.assertRaises(errors.ConsistencyError, self.validate._all, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.tag, 'value'), ast.fetch.Node(ns.bse.filename, 'node')))
+
+ def test_branch(self):
+ # branch accepts correct expressions
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch._Branch(ns.bse.filename)))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this'))))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, 'value')))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, 'node')))
+ # type must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch._Branch(ns.bse.filename))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch.Value(ns.bse.filename, 'value'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch.Node(ns.bse.tag, 'node'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch._Branch(ns.bse.filename))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.Value(ns.bse.filename, 'value'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.Node(ns.bse.tag, 'node'))
+ # predicate must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch._Branch(ns.bse.invalid))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.invalid, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.invalid, 'value'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.invalid, 'node'))
+ # predicate's domain must be related to the type
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch._Branch(ns.bse.label))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.label, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.label, 'node'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.label, 'value'))
+ # predicate's domain cannot be a supertype
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.fetch._Branch(ns.bse.tag))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Node(ns.bse.tag, 'node'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Value(ns.bse.tag, 'value'))
+ # predicate's domain can be a subtype
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch._Branch(ns.bse.filename)))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, 'value')))
+
+ def test_fetch(self):
+ # fetch accepts correct expressions
+ self.assertIsNone(self.validate._fetch(self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bse.label, 'value'))))
+ # range must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._fetch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.filename, ast.fetch.This('this')))
+ # child expression must be valid
+ self.assertRaises(errors.ConsistencyError, self.validate._fetch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Node(ns.bse.label, 'node')))
+
+ def test_named(self):
+ # named accepts correct expressions
+ self.assertIsNone(self.validate._named(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, 'node')))
+ self.assertIsNone(self.validate._named(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, 'value')))
+ # name must be non-empty
+ self.assertRaises(errors.BackendError, self.validate._named, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, ''))
+ self.assertRaises(errors.BackendError, self.validate._named, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, ''))
+
+ def test_node(self):
+ # node accepts correct expressions
+ self.assertIsNone(self.validate._node(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, 'node')))
+ # range must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._node, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.filename, 'node'))
+
+ def test_value(self):
+ # value accepts correct expressions
+ self.assertIsNone(self.validate._value(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, 'value')))
+ # range must be a literal
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.tag, 'value'))
+
+ def test_this(self):
+ # this accepts correct expressions
+ self.assertIsNone(self.validate._this(self.schema.node(ns.bsfs.Entity), ast.fetch.This('this')))
+ # type must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._this, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch.This('this'))
+ self.assertRaises(errors.ConsistencyError, self.validate._this, self.schema.predicate(ns.bsfs.Predicate),
+ ast.fetch.This('this'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._this, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.This('this'))
+ # name must be non-empty
+ self.assertRaises(errors.BackendError, self.validate._this, self.schema.node(ns.bsfs.Entity), ast.fetch.This(''))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/schema/test_schema.py b/test/schema/test_schema.py
index 888cdca..f52cf95 100644
--- a/test/schema/test_schema.py
+++ b/test/schema/test_schema.py
@@ -1,16 +1,11 @@
-"""
-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.schema import types, from_string
from bsfs.utils import errors
# objects to test
@@ -19,6 +14,8 @@ from bsfs.schema.schema import Schema
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestSchema(unittest.TestCase):
def setUp(self):
@@ -26,8 +23,9 @@ class TestSchema(unittest.TestCase):
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 bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
@@ -35,7 +33,8 @@ class TestSchema(unittest.TestCase):
bsfs:Unused rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsfs:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
xsd:boolean rdfs:subClassOf bsfs:Literal .
bse:tag rdfs:subClassOf bsfs:Predicate ;
@@ -55,32 +54,43 @@ class TestSchema(unittest.TestCase):
'''
# nodes
- self.n_root = types.Node(ns.bsfs.Node, None)
- self.n_ent = types.Node(ns.bsfs.Entity, types.Node(ns.bsfs.Node, None))
- self.n_img = types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Entity, types.Node(ns.bsfs.Node, None)))
- self.n_tag = types.Node(ns.bsfs.Tag, types.Node(ns.bsfs.Node, None))
- self.n_unused = types.Node(ns.bsfs.Unused, types.Node(ns.bsfs.Node, None))
+ self.n_root = types.ROOT_NODE
+ self.n_ent = self.n_root.child(ns.bsfs.Entity)
+ self.n_img = self.n_ent.child(ns.bsfs.Image)
+ self.n_tag = self.n_root.child(ns.bsfs.Tag)
+ self.n_unused = self.n_root.child(ns.bsfs.Unused)
self.nodes = [self.n_root, self.n_ent, self.n_img, self.n_tag, self.n_unused]
# literals
- self.l_root = types.Literal(ns.bsfs.Literal, None)
- self.l_string = types.Literal(ns.xsd.string, types.Literal(ns.bsfs.Literal, None))
- self.l_integer = types.Literal(ns.xsd.integer, types.Literal(ns.bsfs.Literal, None))
- self.l_unused = types.Literal(ns.xsd.boolean, types.Literal(ns.bsfs.Literal, None))
- self.literals = [self.l_root, self.l_string, self.l_integer, self.l_unused]
+ self.l_root = types.ROOT_LITERAL
+ self.l_number = types.ROOT_NUMBER
+ self.l_blob = types.ROOT_BLOB
+ self.l_array = types.ROOT_ARRAY
+ self.l_time = types.ROOT_TIME
+ self.l_string = self.l_root.child(ns.xsd.string)
+ self.l_integer = self.l_root.child(ns.xsd.integer)
+ self.l_unused = self.l_root.child(ns.xsd.boolean)
+ self.f_root = types.ROOT_FEATURE
+ self.literals = [self.l_root, self.l_array, self.f_root, self.l_number, self.l_time, self.l_string, self.l_integer, self.l_unused, self.l_blob]
# predicates
- self.p_root = types.Predicate(ns.bsfs.Predicate, None, types.Node(ns.bsfs.Node, None), None, False)
- self.p_tag = self.p_root.get_child(ns.bse.tag, self.n_ent, self.n_tag, False)
- self.p_group = self.p_tag.get_child(ns.bse.group, self.n_img, self.n_tag, False)
- self.p_comment = self.p_root.get_child(ns.bse.comment, self.n_root, self.l_string, True)
+ self.p_root = types.ROOT_PREDICATE
+ self.p_tag = self.p_root.child(ns.bse.tag, self.n_ent, self.n_tag, False)
+ self.p_group = self.p_tag.child(ns.bse.group, self.n_img, self.n_tag, False)
+ self.p_comment = self.p_root.child(ns.bse.comment, self.n_root, self.l_string, True)
self.predicates = [self.p_root, self.p_tag, self.p_group, self.p_comment]
def test_construction(self):
+ # no args yields a minimal schema
+ schema = Schema()
+ self.assertSetEqual(set(schema.nodes()), {self.n_root})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_number, self.l_array, self.l_time, self.f_root, self.l_blob})
+ self.assertSetEqual(set(schema.predicates()), {self.p_root})
+
# nodes and literals are optional
schema = Schema(self.predicates)
self.assertSetEqual(set(schema.nodes()), {self.n_root, self.n_ent, self.n_img, self.n_tag})
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number, self.l_time, self.l_array, self.f_root, self.l_blob})
self.assertSetEqual(set(schema.predicates()), set(self.predicates))
# predicates, nodes, and literals are respected
@@ -101,19 +111,19 @@ class TestSchema(unittest.TestCase):
# literals are complete
schema = Schema(self.predicates, self.nodes, None)
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number, self.l_array, self.l_time, self.f_root, self.l_blob})
schema = Schema(self.predicates, self.nodes, [])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number, self.l_array, self.l_time, self.f_root, self.l_blob})
schema = Schema(self.predicates, self.nodes, [self.l_string])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_number, self.l_array, self.l_time, self.f_root, self.l_blob})
schema = Schema(self.predicates, self.nodes, [self.l_integer])
- self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_integer})
+ self.assertSetEqual(set(schema.literals()), {self.l_root, self.l_string, self.l_integer, self.l_number, self.l_array, self.l_time, self.f_root, self.l_blob})
schema = Schema(self.predicates, self.nodes, [self.l_integer, self.l_unused])
self.assertSetEqual(set(schema.literals()), set(self.literals))
# predicates are complete
schema = Schema([], self.nodes, self.literals)
- self.assertSetEqual(set(schema.predicates()), set())
+ self.assertSetEqual(set(schema.predicates()), {self.p_root})
schema = Schema([self.p_group], self.nodes, self.literals)
self.assertSetEqual(set(schema.predicates()), {self.p_root, self.p_tag, self.p_group})
schema = Schema([self.p_group, self.p_comment], self.nodes, self.literals)
@@ -153,20 +163,27 @@ class TestSchema(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, Schema,
{}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {}, {types.Node(ns.bsfs.Foo, None)})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {}, {types.Node(ns.bsfs.Foo, None)})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {types.Node(ns.bsfs.Foo, None)}, {})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {types.Node(ns.bsfs.Foo, None)}, {})
self.assertRaises(errors.ConsistencyError, Schema,
- {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), None, False)}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
+ {types.Predicate(ns.bsfs.Foo, None, types.Node(ns.bsfs.Node, None), types.ROOT_VERTEX, False)}, {types.Node(ns.bsfs.Foo, None)}, {types.Node(ns.bsfs.Foo, None)})
+
def test_str(self):
+ # string conversion
self.assertEqual(str(Schema([])), 'Schema()')
self.assertEqual(str(Schema([], [], [])), 'Schema()')
self.assertEqual(str(Schema(self.predicates, self.nodes, self.literals)), 'Schema()')
- self.assertEqual(repr(Schema([])), 'Schema([], [], [])')
- self.assertEqual(repr(Schema([], [], [])), 'Schema([], [], [])')
+ # repr conversion with only default nodes, literals, and predicates
+ n = [ns.bsfs.Node]
+ l = [ns.bsfs.Literal, ns.bsl.Array, ns.bsl.Array.Feature, ns.bsl.BinaryBlob, ns.bsl.Number, ns.bsl.Time]
+ p = [ns.bsfs.Predicate]
+ self.assertEqual(repr(Schema()), f'Schema({n}, {l}, {p})')
+ self.assertEqual(repr(Schema([], [], [])), f'Schema({n}, {l}, {p})')
+ # repr conversion
n = [ns.bsfs.Entity, ns.bsfs.Image, ns.bsfs.Node, ns.bsfs.Tag, ns.bsfs.Unused]
- l = [ns.bsfs.Literal, ns.xsd.boolean, ns.xsd.integer, ns.xsd.string]
+ l = [ns.xsd.boolean, ns.xsd.integer, ns.xsd.string, ns.bsfs.Literal, ns.bsl.Array, ns.bsl.Array.Feature, ns.bsl.BinaryBlob, ns.bsl.Number, ns.bsl.Time]
p = [ns.bse.comment, ns.bse.group, ns.bse.tag, ns.bsfs.Predicate]
self.assertEqual(repr(Schema(self.predicates, self.nodes, self.literals)), f'Schema({n}, {l}, {p})')
@@ -202,16 +219,16 @@ class TestSchema(unittest.TestCase):
self.assertNotEqual(hash(schema),
hash(Schema([self.p_group, self.p_tag, self.p_root], self.nodes, self.literals)))
self.assertNotEqual(schema,
- Schema(self.predicates + [self.p_root.get_child(ns.bse.filesize, self.n_ent, self.l_integer)], self.nodes, self.literals))
+ Schema(self.predicates + [self.p_root.child(ns.bse.filesize, self.n_ent, self.l_integer)], self.nodes, self.literals))
self.assertNotEqual(hash(schema),
- hash(Schema(self.predicates + [self.p_root.get_child(ns.bse.filesize, self.n_ent, self.l_integer)], self.nodes, self.literals)))
+ hash(Schema(self.predicates + [self.p_root.child(ns.bse.filesize, self.n_ent, self.l_integer)], self.nodes, self.literals)))
def test_order(self):
# setup
class Foo(): pass
- p_foo = self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, True)
- p_sub = p_foo.get_child(ns.bse.sub, self.n_ent, self.l_string, True)
- p_bar = self.p_root.get_child(ns.bse.bar, self.n_ent, self.l_string, True)
+ p_foo = self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, True)
+ p_sub = p_foo.child(ns.bse.sub, self.n_ent, self.l_string, True)
+ p_bar = self.p_root.child(ns.bse.bar, self.n_ent, self.l_string, True)
# can only compare schema to other schema
# <
@@ -258,11 +275,11 @@ class TestSchema(unittest.TestCase):
self.assertTrue(operator.lt(Schema({self.p_tag}), Schema({self.p_group})))
self.assertTrue(operator.le(Schema({self.p_tag}), Schema({self.p_group})))
# subset considers differences in predicates and literals
- self.assertTrue(operator.lt(Schema.Empty(), Schema({self.p_comment})))
+ self.assertTrue(operator.lt(Schema(), Schema({self.p_comment})))
# subset considers differences in predicates, nodes, and literals
- self.assertTrue(operator.lt(Schema({}), Schema.Empty()))
- self.assertTrue(operator.lt(Schema({self.p_tag}), Schema.from_string(self.schema_str)))
- self.assertTrue(operator.le(Schema({self.p_tag}), Schema.from_string(self.schema_str)))
+ self.assertTrue(operator.le(Schema({}), Schema()))
+ self.assertTrue(operator.lt(Schema({self.p_tag}), from_string(self.schema_str)))
+ self.assertTrue(operator.le(Schema({self.p_tag}), from_string(self.schema_str)))
self.assertFalse(operator.lt(Schema({self.p_comment}), Schema({self.p_tag})))
self.assertFalse(operator.le(Schema({self.p_comment}), Schema({self.p_tag})))
@@ -280,54 +297,54 @@ class TestSchema(unittest.TestCase):
self.assertTrue(operator.gt(Schema({self.p_group}), Schema({self.p_tag})))
self.assertTrue(operator.ge(Schema({self.p_group}), Schema({self.p_tag})))
# superset considers differences in predicates and literals
- self.assertTrue(operator.gt(Schema({self.p_comment}), Schema.Empty()))
+ self.assertTrue(operator.gt(Schema({self.p_comment}), Schema()))
# superset considers differences in predicates, nodes, and literals
- self.assertTrue(operator.gt(Schema.Empty(), Schema({})))
- self.assertTrue(operator.gt(Schema.from_string(self.schema_str), Schema({self.p_tag})))
- self.assertTrue(operator.ge(Schema.from_string(self.schema_str), Schema({self.p_tag})))
+ self.assertTrue(operator.ge(Schema(), Schema({})))
+ self.assertTrue(operator.gt(from_string(self.schema_str), Schema({self.p_tag})))
+ self.assertTrue(operator.ge(from_string(self.schema_str), Schema({self.p_tag})))
self.assertFalse(operator.gt(Schema({self.p_tag}), Schema({self.p_comment})))
self.assertFalse(operator.ge(Schema({self.p_tag}), Schema({self.p_comment})))
# inconsistent schema cannot be a subset
self.assertFalse(operator.le(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
self.assertFalse(operator.le(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
+ self.p_root.child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
self.assertFalse(operator.le(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
self.assertFalse(operator.le(Schema({}, {self.n_img}), Schema({}, {
types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Node, None))})))
self.assertFalse(operator.le(Schema({}, {}, {self.l_integer}), Schema({}, {}, {
types.Literal(ns.xsd.integer, types.Literal(ns.xsd.number, types.Literal(ns.bsfs.Literal, None)))})))
# inconsistent schema cannot be a true subset
self.assertFalse(operator.lt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
self.assertFalse(operator.lt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
+ self.p_root.child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
self.assertFalse(operator.lt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
self.assertFalse(operator.lt(Schema({}, {self.n_img}), Schema({}, {
types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Node, None))})))
self.assertFalse(operator.lt(Schema({}, {}, {self.l_integer}), Schema({}, {}, {
types.Literal(ns.xsd.integer, types.Literal(ns.xsd.number, types.Literal(ns.bsfs.Literal, None)))})))
# inconsistent schema cannot be a superset
self.assertFalse(operator.ge(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
self.assertFalse(operator.ge(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
+ self.p_root.child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
self.assertFalse(operator.ge(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
self.assertFalse(operator.ge(Schema({}, {self.n_img}), Schema({}, {
types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Node, None))})))
self.assertFalse(operator.ge(Schema({}, {}, {self.l_integer}), Schema({}, {}, {
types.Literal(ns.xsd.integer, types.Literal(ns.xsd.number, types.Literal(ns.bsfs.Literal, None)))})))
# inconsistent schema cannot be a true superset
self.assertFalse(operator.gt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_integer, True)}))) # inconsistent w.r.t. literal
self.assertFalse(operator.gt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
+ self.p_root.child(ns.bse.foo, self.n_img, self.l_string, True)}))) # inconsistent w.r.t. node
self.assertFalse(operator.gt(Schema({p_foo}), Schema({
- self.p_root.get_child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
+ self.p_root.child(ns.bse.foo, self.n_ent, self.l_string, False)}))) # inconsistent w.r.t. unique
self.assertFalse(operator.gt(Schema({}, {self.n_img}), Schema({}, {
types.Node(ns.bsfs.Image, types.Node(ns.bsfs.Node, None))})))
self.assertFalse(operator.gt(Schema({}, {}, {self.l_integer}), Schema({}, {}, {
@@ -351,26 +368,26 @@ class TestSchema(unittest.TestCase):
# difference does not contain predicates from the RHS
diff = Schema({self.p_tag, self.p_comment}).diff(Schema({self.p_group}))
self.assertSetEqual(set(diff.nodes), set())
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_string})
+ self.assertSetEqual(set(diff.literals), {self.l_string})
self.assertSetEqual(set(diff.predicates), {self.p_comment})
# difference considers extra nodes and literals
diff = Schema({self.p_tag}, {self.n_unused}, {self.l_unused}).diff(Schema({self.p_tag}))
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_unused})
self.assertSetEqual(set(diff.predicates), set())
# difference considers inconsistent types
diff = Schema({self.p_tag}, {self.n_unused}, {self.l_unused}).diff(
Schema({self.p_tag}, {types.Node(ns.bsfs.Unused, None)}, {types.Literal(ns.xsd.boolean, None)}))
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_unused})
self.assertSetEqual(set(diff.predicates), set())
# __sub__ is an alias for diff
diff = Schema({self.p_comment}, {self.n_unused}, {self.l_unused}) - Schema({self.p_group})
self.assertSetEqual(set(diff.nodes), {self.n_unused})
- self.assertSetEqual(set(diff.literals), {self.l_root, self.l_string, self.l_unused})
+ self.assertSetEqual(set(diff.literals), {self.l_string, self.l_unused})
self.assertSetEqual(set(diff.predicates), {self.p_comment})
# __sub__ only accepts Schema instances
class Foo(): pass
@@ -547,196 +564,6 @@ class TestSchema(unittest.TestCase):
self.assertFalse(schema.has_predicate(ns.bse.mimetype))
self.assertFalse(schema.has_predicate(self.p_root))
- def test_empty(self):
- self.assertEqual(Schema.Empty(), Schema(
- [types.Predicate(ns.bsfs.Predicate, None, types.Node(ns.bsfs.Node, None), None, False)],
- [types.Node(ns.bsfs.Node, None)],
- [types.Literal(ns.bsfs.Literal, None)],
- ))
-
- def test_from_string(self):
- # from_string creates a schema
- self.assertEqual(
- Schema(self.predicates, self.nodes, self.literals),
- Schema.from_string(self.schema_str))
-
- # schema contains at least the root types
- self.assertEqual(Schema.from_string(''), Schema({self.p_root}, {self.n_root}, {self.l_root}))
-
- # custom example
- self.assertEqual(
- Schema({types.Predicate(ns.bsfs.Predicate, None, self.n_root, None, False).get_child(
- ns.bse.filename, self.n_ent, self.l_string, False)}),
- Schema.from_string('''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- xsd:string rdfs:subClassOf bsfs:Literal .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- '''))
-
- # all nodes must be defined
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- xsd:string rdfs:subClassOf bsfs:Literal .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # all literals must be defined
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # must not have circular dependencies
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- # ah, a nice circular dependency
- bsfs:Entity rdfs:subClassOf bsfs:Document .
- bsfs:Document rdfs:subClassOf bsfs:Entity .
- bsfs:PDF rdfs:subClassOf bsfs:Document .
- ''')
-
- # range must be a node or literal
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range xsd:string ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range bsfs:Foo ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:filename rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity ;
- rdfs:range bsfs:Predicate ;
- bsfs:unique "false"^^xsd:boolean .
- ''')
-
- # must be consistent
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
- bsfs:Document rdfs:subClassOf bsfs:Node .
- bsfs:Document rdfs:subClassOf bsfs:Entity.
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
-
- xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:name rdfs:subClassOf bsfs:Literal .
- xsd:name rdfs:subClassOf xsd:string .
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Entity .
-
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:range bsfs:Entity .
-
- ''')
- self.assertRaises(errors.ConsistencyError, Schema.from_string, '''
- prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
-
- bsfs:Entity rdfs:subClassOf bsfs:Node .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- rdfs:domain bsfs:Node ;
- rdfs:range bsfs:Node ;
- bsfs:unique "false"^^xsd:boolean .
-
- bse:foo rdfs:subClassOf bsfs:Predicate ;
- bsfs:unique "true"^^xsd:boolean .
-
- ''')
-
-
-
## main ##
if __name__ == '__main__':
diff --git a/test/schema/test_serialize.py b/test/schema/test_serialize.py
new file mode 100644
index 0000000..7d5d3ae
--- /dev/null
+++ b/test/schema/test_serialize.py
@@ -0,0 +1,1048 @@
+
+# imports
+import re
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.schema import Schema, types
+from bsfs.utils import errors, URI
+
+# objects to test
+from bsfs.schema.serialize import from_string, to_string
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+
+class TestFromString(unittest.TestCase):
+
+ def test_empty(self):
+ # schema contains at least the root types
+ self.assertEqual(from_string(''), Schema())
+
+
+ def test_circular_dependency(self):
+ # must not have circular dependencies
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ # ah, a nice circular dependency
+ bsfs:Entity rdfs:subClassOf bsfs:Document .
+ bsfs:Document rdfs:subClassOf bsfs:Entity .
+ bsfs:PDF rdfs:subClassOf bsfs:Document .
+ ''')
+
+
+ def test_node(self):
+ # all nodes must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # node definitions must be consistent (cannot re-use a node uri)
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Document rdfs:subClassOf bsfs:Node .
+ bsfs:Document rdfs:subClassOf bsfs:Entity . # conflicting parent
+ ''')
+
+ # additional nodes can be defined
+ n_unused = types.ROOT_NODE.child(ns.bsfs.unused)
+ self.assertEqual(Schema({}, {n_unused}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:unused rdfs:subClassOf bsfs:Node . # unused symbol
+ '''))
+
+ # a node can have multiple children
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ n_tag = types.ROOT_NODE.child(ns.bsfs.Tag)
+ n_doc = n_ent.child(ns.bsfs.Document)
+ n_image = n_ent.child(ns.bsfs.Image)
+ self.assertEqual(Schema({}, {n_ent, n_tag, n_doc, n_image}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ # nodes inherit from same parent
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+
+ # nodes inherit from same parent
+ bsfs:Document rdfs:subClassOf bsfs:Entity .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+ '''))
+
+ # additional nodes can be defined and used
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_filename = types.ROOT_PREDICATE.child(ns.bse.filename,
+ n_ent, l_string, False)
+ self.assertEqual(Schema({p_filename}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # nodes can have annotations
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ ''').node(ns.bsfs.Entity).annotations, {})
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').node(ns.bsfs.Entity).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_literal(self):
+ # all literals must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ; # undefined symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # literal definitions must be consistent (cannot re-use a literal uri)
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:name rdfs:subClassOf bsfs:Literal .
+ xsd:name rdfs:subClassOf xsd:string . # conflicting parent
+ ''')
+
+ # additional literals can be defined
+ l_unused = types.ROOT_LITERAL.child(ns.xsd.unused)
+ self.assertEqual(Schema({}, {}, {l_unused}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ xsd:unused rdfs:subClassOf bsfs:Literal . # unused symbol
+ '''))
+
+ # a literal can have multiple children
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ l_integer = types.ROOT_NUMBER.child(ns.xsd.integer)
+ l_unsigned = l_integer.child(ns.xsd.unsigned)
+ l_signed = l_integer.child(ns.xsd.signed)
+ self.assertEqual(Schema({}, {}, {l_string, l_integer, l_unsigned, l_signed}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+
+ # literals inherit from same parent
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
+
+ # literals inherit from same parent
+ xsd:unsigned rdfs:subClassOf xsd:integer .
+ xsd:signed rdfs:subClassOf xsd:integer .
+ '''))
+
+ # additional literals can be defined and used
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_filename = types.ROOT_PREDICATE.child(ns.bse.filename,
+ n_ent, l_string, False)
+ self.assertEqual(Schema({p_filename}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # literals can have annotations
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ ''').literal(ns.xsd.string).annotations, {})
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+
+ xsd:string rdfs:subClassOf bsfs:Literal ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').literal(ns.xsd.string).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_predicate(self):
+ # domain must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ; # undefined symbol
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # domain cannot be a literal
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Literal .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ; # literal instead of node
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # range must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ; # undefined symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # range must be defined
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Foo ; # undefined symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+ # range must be a node or a literal
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Predicate ; # invalid symbol
+ bsfs:unique "false"^^xsd:boolean .
+ ''')
+
+ # additional predicates can be defined
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_comment = types.ROOT_PREDICATE.child(ns.bse.comment, domain=n_ent, range=l_string, unique=False)
+ self.assertEqual(Schema({p_comment}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # predicates inherit properties from parents
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation, domain=n_ent, range=l_string)
+ p_comment = p_annotation.child(ns.bse.comment, unique=True)
+ self.assertEqual(Schema({p_comment}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string .
+
+ bse:comment rdfs:subClassOf bsfs:Annotation ; # inherits domain/range from bsfs:Annotation
+ bsfs:unique "true"^^xsd:boolean .
+ '''))
+
+ # we can define partial predicates (w/o specifying a usable range)
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation, domain=n_ent)
+ p_comment = p_annotation.child(ns.bse.comment, range=l_string, unique=False)
+ self.assertEqual(Schema({p_comment}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate ; # derive predicate w/o setting range
+ rdfs:domain bsfs:Entity .
+
+ bse:comment rdfs:subClassOf bsfs:Annotation ; # derived predicate w/ setting range
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+ '''))
+
+ # predicate definition can be split across multiple statements.
+ # statements can be repeated
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ p_foo = types.ROOT_PREDICATE.child(ns.bse.foo, domain=n_ent, range=types.ROOT_NODE, unique=True)
+ self.assertEqual(Schema({p_foo}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity.
+ '''))
+
+ # domain must be a subtype of parent's domain
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ n_image = n_ent.child(ns.bsfs.Image)
+ p_foo = types.ROOT_PREDICATE.child(ns.bse.foo, domain=types.ROOT_NODE)
+ p_bar = p_foo.child(ns.bse.bar, domain=n_ent)
+ p_foobar = p_bar.child(ns.bse.foobar, domain=n_image)
+ self.assertEqual(Schema({p_foobar}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node .
+ bse:bar rdfs:subClassOf bse:foo ;
+ rdfs:domain bsfs:Entity .
+ bse:foobar rdfs:subClassOf bse:bar ;
+ rdfs:domain bsfs:Image .
+ '''))
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Image .
+ bse:bar rdfs:subClassOf bse:foo ;
+ rdfs:domain bsfs:Entity .
+ bse:foobar rdfs:subClassOf bse:bar ;
+ rdfs:domain bsfs:Node .
+ ''')
+
+ # range must be a subtype of parent's range
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ n_image = n_ent.child(ns.bsfs.Image)
+ p_foo = types.ROOT_PREDICATE.child(ns.bse.foo, range=types.ROOT_NODE)
+ p_bar = p_foo.child(ns.bse.bar, range=n_ent)
+ p_foobar = p_bar.child(ns.bse.foobar, range=n_image)
+ self.assertEqual(Schema({p_foobar}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Node .
+ bse:bar rdfs:subClassOf bse:foo ;
+ rdfs:range bsfs:Entity .
+ bse:foobar rdfs:subClassOf bse:bar ;
+ rdfs:range bsfs:Image .
+ '''))
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Image .
+ bse:bar rdfs:subClassOf bse:foo ;
+ rdfs:range bsfs:Entity .
+ bse:foobar rdfs:subClassOf bse:bar ;
+ rdfs:range bsfs:Node .
+ ''')
+
+ # cannot define the same predicate from multiple parents
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Annotation ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ # cannot assign multiple conflicting domains to the same predicate
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity . # conflicting domain
+ ''')
+ # cannot assign multiple conflicting ranges to the same predicate
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Entity . # conflicting range
+ ''')
+ # cannot assign multiple conflicting uniques to the same predicate
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:foo rdfs:subClassOf bsfs:Predicate ;
+ bsfs:unique "true"^^xsd:boolean . # conflicting unique
+ ''')
+
+ # predicates can have annotations
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Node .
+
+ ''').predicate(ns.bse.comment).annotations, {})
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:range bsfs:Node ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').predicate(ns.bse.comment).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_feature(self):
+ # additional features can be defined
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors)
+ self.assertEqual(Schema(literals={f_colors}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature .
+
+ '''))
+
+ # features inherit properties from parents
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.dtype().i32)
+ f_main_colors = f_colors.child(ns.bsfs.MainColor, distance=ns.bsfs.cosine, dtype=ns.bsfs.dtype().f16)
+ self.assertEqual(Schema(literals={f_colors, f_main_colors}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ; # inherits distance from bsa:Feature
+ bsfs:dimension "1234"^^xsd:integer ; # overwrites bsa:Feature
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#i32> . # overwrites bsa:Feature
+
+ bsfs:MainColor rdfs:subClassOf bsfs:Colors ; # inherits dimension from bsfs:Colors
+ bsfs:distance bsfs:cosine ; # overwrites bsa:Feature
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f16> . # overwrites bsfs:Colors
+
+ '''))
+
+ # feature definition can be split across multiple statements.
+ # statements can be repeated
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.dtype().f32)
+ self.assertEqual(Schema(literals={f_colors}), from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer ; # non-conflicting repetition
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f32> .
+ '''))
+
+ # cannot define the same feature from multiple parents
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+ bsfs:ColorSpace rdfs:subClassOf bsa:Feature .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature .
+ bsfs:Colors rdfs:subClassOf bsfs:ColorSpace .
+
+ ''')
+ # cannot assign multiple conflicting dimensions to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "4321"^^xsd:integer . # conflicting dimension
+
+ ''')
+ # cannot assign multiple conflicting dtypes to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#i32> .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f16> . # conflicting dtype
+ ''')
+ # cannot assign multiple conflicting distance metrics to the same feature
+ self.assertRaises(errors.ConsistencyError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:distance bsfs:euclidean .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:distance bsfs:cosine . # conflicting distance
+ ''')
+
+ # features can have annotations
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer .
+
+ ''').literal(ns.bsfs.Colors).annotations, {})
+ self.assertDictEqual(from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "1234"^^xsd:integer ;
+ rdfs:label "hello world"^^xsd:string ;
+ bsfs:foo "1234"^^xsd:integer .
+
+ ''').literal(ns.bsfs.Colors).annotations, {
+ ns.rdfs.label: 'hello world',
+ ns.bsfs.foo: 1234,
+ })
+
+
+ def test_integration(self):
+ # nodes
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ n_tag = types.ROOT_NODE.child(ns.bsfs.Tag)
+ n_image = n_ent.child(ns.bsfs.Image)
+ # literals
+ l_string = types.ROOT_LITERAL.child(ns.xsd.string)
+ l_array = types.ROOT_LITERAL.child(ns.bsfs.array)
+ l_integer = types.ROOT_NUMBER.child(ns.xsd.integer)
+ l_boolean = types.ROOT_LITERAL.child(ns.xsd.boolean)
+ # predicates
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation)
+ p_tag = types.ROOT_PREDICATE.child(ns.bse.tag, domain=n_ent, range=n_tag)
+ p_group = p_tag.child(ns.bse.group, domain=n_image, unique=True)
+ p_comment = p_annotation.child(ns.bse.comment, range=l_string)
+ # features
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial'),
+ dtype=ns.bsfs.dtype().f16, distance=ns.bsfs.euclidean)
+ f_colors1234 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial#1234'), dimension=1024)
+ f_colors4321 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial#4321'), dimension=2048)
+ # schema
+ ref = Schema(
+ {p_annotation, p_tag, p_group, p_comment},
+ {n_ent, n_tag, n_image},
+ {l_string, l_integer, l_boolean, f_colors, f_colors1234, f_colors4321})
+ # load from string
+ gen = from_string('''
+ # generic prefixes
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ # bsfs prefixes
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ # nodes
+ bsfs:Entity rdfs:subClassOf bsfs:Node ;
+ rdfs:label "Principal node"^^xsd:string .
+ bsfs:Tag rdfs:subClassOf bsfs:Node ;
+ rdfs:label "Tag"^^xsd:string .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+
+ # literals
+ xsd:string rdfs:subClassOf bsfs:Literal ;
+ rdfs:label "A sequence of characters"^^xsd:string .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+
+
+ # abstract predicates
+ bsfs:Annotation rdfs:subClassOf bsfs:Predicate ;
+ rdfs:label "node annotation"^^xsd:string .
+
+ # feature instances
+ <https://schema.bsfs.io/core/Feature/colors_spatial> rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f16> ;
+ bsfs:distance bsfs:euclidean ;
+ # annotations
+ rdfs:label "ColorsSpatial instances. Dimension depends on instance."^^xsd:string ;
+ bsfs:first_arg "1234"^^xsd:integer ;
+ bsfs:second_arg "hello world"^^xsd:string .
+
+ <https://schema.bsfs.io/core/Feature/colors_spatial#1234> rdfs:subClassOf <https://schema.bsfs.io/core/Feature/colors_spatial> ;
+ bsfs:dimension "1024"^^xsd:integer ;
+ rdfs:label "Main colors spatial instance"^^xsd:string .
+
+ <https://schema.bsfs.io/core/Feature/colors_spatial#4321> rdfs:subClassOf <https://schema.bsfs.io/core/Feature/colors_spatial> ;
+ bsfs:dimension "2048"^^xsd:integer .
+
+ # predicate instances
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean ;
+ # annotations
+ rdfs:label "connect entity to a tag"^^xsd:string .
+
+ bse:group rdfs:subClassOf bse:tag ; # subtype of another predicate
+ rdfs:domain bsfs:Image ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:comment rdfs:subClassOf bsfs:Annotation ; # subtype of abstract predicate
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ # schemas are equal
+ self.assertEqual(ref, gen)
+ # check annotations
+ self.assertDictEqual(gen.node(ns.bsfs.Entity).annotations, {ns.rdfs.label: 'Principal node'})
+ self.assertDictEqual(gen.node(ns.bsfs.Tag).annotations, {ns.rdfs.label: 'Tag'})
+ self.assertDictEqual(gen.literal(ns.xsd.string).annotations, {ns.rdfs.label: 'A sequence of characters'})
+ self.assertDictEqual(gen.predicate(ns.bsfs.Annotation).annotations, {ns.rdfs.label: 'node annotation'})
+ self.assertDictEqual(gen.literal(URI('https://schema.bsfs.io/core/Feature/colors_spatial')).annotations, {
+ ns.rdfs.label: 'ColorsSpatial instances. Dimension depends on instance.',
+ ns.bsfs.first_arg: 1234,
+ ns.bsfs.second_arg: 'hello world',
+ })
+ self.assertDictEqual(gen.literal(URI('https://schema.bsfs.io/core/Feature/colors_spatial#1234')).annotations, {
+ ns.rdfs.label: 'Main colors spatial instance'})
+ self.assertDictEqual(gen.predicate(ns.bse.tag).annotations, {ns.rdfs.label: 'connect entity to a tag'})
+
+ # blank nodes result in an error
+ self.assertRaises(errors.BackendError, from_string, '''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ bsfs:Entity rdfs:subClassOf bsfs:Node ;
+ bsfs:foo _:bar .
+ ''')
+
+
+class TestToString(unittest.TestCase):
+
+ def test_empty(self):
+ self.assertEqual(Schema(), from_string(to_string(Schema())))
+
+ def test_parse(self):
+ schema = Schema()
+ schema._nodes[ns.bsfs.Invalid] = 123 # NOTE: Access protected to force an invalid schema
+ self.assertRaises(TypeError, to_string, schema)
+
+ def test_literal(self):
+ # root literals
+ l_str = types.ROOT_LITERAL.child(ns.xsd.string)
+ # derived literals
+ l_int = types.ROOT_NUMBER.child(ns.xsd.integer)
+ l_unsigned = l_int.child(ns.xsd.unsigned)
+ # create schema
+ schema = Schema(literals={l_int, l_str, l_unsigned})
+
+ schema_str = to_string(schema)
+ # all symbols are serialized
+ self.assertIn('xsd:string', schema_str)
+ self.assertIn('xsd:integer', schema_str)
+ self.assertIn('xsd:unsigned', schema_str)
+ # unserialize yields the original schema
+ self.assertEqual(schema, from_string(schema_str))
+
+ # literals that have no parent are ignored
+ schema = Schema(literals={types.Literal(ns.bsfs.Invalid, None)})
+ self.assertEqual(Schema(), from_string(to_string(schema)))
+ self.assertNotIn('Invalid', to_string(schema))
+
+ # literal annotations are serialized
+ annotations = {
+ ns.rdfs.label: 'hello world',
+ ns.schema.description: 'some text',
+ ns.bsfs.foo: 1234,
+ ns.bsfs.bar: True,
+ }
+ l_str = types.ROOT_LITERAL.child(ns.xsd.string, **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema(literals={l_str}))).literal(ns.xsd.string).annotations)
+
+
+ def test_node(self):
+ # root nodes
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ n_tag = types.ROOT_NODE.child(ns.bsfs.Tag)
+ # derived nodes
+ n_img = n_ent.child(ns.bsfs.Image)
+ n_doc = n_ent.child(ns.bsfs.Document)
+ n_grp = n_tag.child(ns.bsfs.Group)
+ # create schema
+ schema = Schema(nodes={n_ent, n_img, n_doc, n_tag, n_grp})
+
+ schema_str = to_string(schema)
+ # all symbols are serialized
+ self.assertIn('bsfs:Entity', schema_str)
+ self.assertIn('bsfs:Tag', schema_str)
+ self.assertIn('bsfs:Image', schema_str)
+ self.assertIn('bsfs:Document', schema_str)
+ self.assertIn('bsfs:Group', schema_str)
+ # unserialize yields the original schema
+ self.assertEqual(schema, from_string(schema_str))
+
+ # nodes that have no parent are ignored
+ schema = Schema(nodes={types.Node(ns.bsfs.Invalid, None)})
+ self.assertEqual(Schema(), from_string(to_string(schema)))
+ self.assertNotIn('Invalid', to_string(schema))
+
+ # node annotations are serialized
+ annotations = {
+ ns.rdfs.label: 'hello world',
+ ns.schema.description: 'some text',
+ ns.bsfs.foo: 1234,
+ ns.bsfs.bar: True,
+ }
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity, **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema(nodes={n_ent}))).node(ns.bsfs.Entity).annotations)
+
+
+ def test_predicate(self):
+ # auxiliary types
+ n_ent = types.ROOT_NODE.child(ns.bsfs.Entity)
+ l_str = types.ROOT_LITERAL.child(ns.xsd.string)
+ # root predicates
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation, domain=n_ent)
+ p_owner = types.ROOT_PREDICATE.child(ns.bse.owner, range=l_str, unique=True)
+ # derived predicates
+ p_comment = p_annotation.child(ns.bse.comment, range=l_str) # inherits domain
+ p_note = p_comment.child(ns.bse.note, unique=True) # inherits domain/range
+ # create schema
+ schema = Schema({p_owner, p_comment, p_note})
+
+ schema_str = to_string(schema)
+ # all symbols are serialized
+ self.assertIn('bsfs:Entity', schema_str)
+ self.assertIn('xsd:string', schema_str)
+ self.assertIn('bsfs:Annotation', schema_str)
+ self.assertIn('bse:comment', schema_str)
+ self.assertIn('bse:owner', schema_str)
+ self.assertIn('bse:note', schema_str)
+ # inherited properties are not serialized
+ self.assertIsNotNone(re.search(r'bse:comment[^\.]*rdfs:range[^\.]', schema_str))
+ self.assertIsNone(re.search(r'bse:comment[^\.]*rdfs:domain[^\.]', schema_str))
+ #p_note has no domain/range
+ self.assertIsNone(re.search(r'bse:note[^\.]*rdfs:domain[^\.]', schema_str))
+ self.assertIsNone(re.search(r'bse:note[^\.]*rdfs:range[^\.]', schema_str))
+ # unserialize yields the original schema
+ self.assertEqual(schema, from_string(schema_str))
+
+ # predicate annotations are serialized
+ annotations = {
+ ns.rdfs.label: 'hello world',
+ ns.schema.description: 'some text',
+ ns.bsfs.foo: 1234,
+ ns.bsfs.bar: False,
+ }
+ p_annotation = types.ROOT_PREDICATE.child(ns.bsfs.Annotation, **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema({p_annotation}))).predicate(ns.bsfs.Annotation).annotations)
+
+
+ def test_feature(self):
+ # root features
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors'),
+ distance=ns.bsfs.cosine)
+ # derived features
+ f_colors1234 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors#1234'),
+ dimension=1024) # inherits dtype, distance
+ f_colors4321 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors#4321'),
+ dimension=2048, distance=ns.bsfs.euclidean) # inherits dtype
+ # create schema
+ schema = Schema(literals={f_colors, f_colors1234, f_colors4321})
+
+ schema_str = to_string(schema)
+ # all symbols are serialized
+ self.assertIn('bsl:Array', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors#1234', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors#4321', schema_str)
+ # inherited properties are not serialized
+ self.assertIsNotNone(re.search(r'<https://schema.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:dimension[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:dtype[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:distance[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:dimension[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:distance[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:dtype[^\.]', schema_str))
+ # unserialize yields the original schema
+ self.assertEqual(schema, from_string(schema_str))
+
+ # predicate annotations are serialized
+ annotations = {
+ ns.rdfs.label: 'hello world',
+ ns.schema.description: 'some text',
+ ns.bsfs.foo: 1234,
+ ns.bsfs.bar: False,
+ }
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors'),
+ dtype=ns.bsfs.dtype().f16, distance=ns.bsfs.euclidean,
+ **annotations)
+ self.assertDictEqual(
+ annotations,
+ from_string(to_string(Schema(literals={f_colors}))).literal(URI('https://schema.bsfs.io/core/Feature/colors')).annotations)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/schema/test_types.py b/test/schema/test_types.py
index 4a49e6e..9bfa8c5 100644
--- a/test/schema/test_types.py
+++ b/test/schema/test_types.py
@@ -1,24 +1,23 @@
-"""
-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.types import ROOT_PREDICATE, ROOT_VERTEX, ROOT_FEATURE
from bsfs.utils import errors
# objects to test
-from bsfs.schema.types import _Type, _Vertex, Node, Literal, Predicate
+from bsfs.schema.types import _Type, Vertex, Node, Literal, Predicate, Feature
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestType(unittest.TestCase):
+
def test_parents(self):
# create some types
fst = _Type('First')
@@ -31,7 +30,25 @@ class TestType(unittest.TestCase):
self.assertListEqual(list(trd.parents()), [snd, fst])
self.assertListEqual(list(frd.parents()), [trd, snd, fst])
- def test_essentials(self):
+ def test_annotations(self):
+ # annotations can be empty
+ self.assertDictEqual(_Type('Foo', None).annotations, {})
+ # annotations are stored
+ self.assertDictEqual(_Type('Foo', None, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123})
+ # comparison ignores annotations
+ self.assertEqual(
+ _Type('Foo', None, foo='bar', bar='foo'),
+ _Type('Foo', None, hello='world', foobar=1234))
+ self.assertEqual(
+ hash(_Type('Foo', None, foo='bar', bar='foo')),
+ hash(_Type('Foo', None, hello='world', foobar=1234)))
+ # annotations can be passed to child
+ self.assertDictEqual(_Type('First', foo='bar').child('Second', bar='foo').annotations, {
+ 'bar': 'foo'})
+
+ def test_string_conversion(self):
# type w/o parent
self.assertEqual(str(_Type('Foo')), '_Type(Foo)')
self.assertEqual(repr(_Type('Foo')), '_Type(Foo, None)')
@@ -51,14 +68,17 @@ class TestType(unittest.TestCase):
self.assertEqual(str(_Type('Foo', SubType('Bar'))), '_Type(Foo)')
self.assertEqual(repr(_Type('Foo', SubType('Bar'))), '_Type(Foo, SubType(Bar, None))')
- def test_get_child(self):
+ def test_child(self):
# callee is used as parent
- self.assertEqual(_Type('First').get_child('Second'), _Type('Second', _Type('First')))
+ self.assertEqual(_Type('First').child('Second'), _Type('Second', _Type('First')))
# works with multiple parents
- self.assertEqual(_Type('First').get_child('Second').get_child('Third'), _Type('Third', _Type('Second', _Type('First'))))
+ self.assertEqual(_Type('First').child('Second').child('Third'), _Type('Third', _Type('Second', _Type('First'))))
# type persists
class Foo(_Type): pass
- self.assertEqual(Foo('First').get_child('Second'), Foo('Second', Foo('First')))
+ self.assertEqual(Foo('First').child('Second'), Foo('Second', Foo('First')))
+ # annotations are respected
+ self.assertDictEqual(_Type('First', foo='bar').child('Second', bar='foo').annotations, {
+ 'bar': 'foo'})
def test_equality(self):
# equality depends on uri
@@ -76,6 +96,13 @@ class TestType(unittest.TestCase):
# comparison respects parent
self.assertNotEqual(_Type('Foo', _Type('Bar')), _Type('Foo'))
self.assertNotEqual(hash(_Type('Foo', _Type('Bar'))), hash(_Type('Foo')))
+ # comparison ignores annotations
+ self.assertEqual(
+ _Type('Foo', None, foo='bar', bar='foo'),
+ _Type('Foo', None, hello='world', foobar=1234))
+ self.assertEqual(
+ hash(_Type('Foo', None, foo='bar', bar='foo')),
+ hash(_Type('Foo', None, hello='world', foobar=1234)))
def test_order(self):
# create some types.
@@ -109,27 +136,43 @@ class TestType(unittest.TestCase):
self.assertFalse(bike > bicycle)
self.assertFalse(bike >= bicycle)
self.assertFalse(bike == bicycle)
+
+ # comparing different classes returns False ...
+ # ... when classes are hierarchically related
class Foo(_Type): pass
- foo = Foo(bike.uri, bike.parent)
- # cannot compare different types
- self.assertRaises(TypeError, operator.lt, foo, bike)
- self.assertRaises(TypeError, operator.le, foo, bike)
- self.assertRaises(TypeError, operator.gt, foo, bike)
- self.assertRaises(TypeError, operator.ge, foo, bike)
+ foo = Foo('Foo', bike)
+ self.assertFalse(foo < bike)
+ self.assertFalse(foo <= bike)
+ self.assertFalse(foo > bike)
+ self.assertFalse(foo >= bike)
# goes both ways
- self.assertRaises(TypeError, operator.lt, bike, foo)
- self.assertRaises(TypeError, operator.le, bike, foo)
- self.assertRaises(TypeError, operator.gt, bike, foo)
- self.assertRaises(TypeError, operator.ge, bike, foo)
+ self.assertFalse(bike < foo)
+ self.assertFalse(bike <= foo)
+ self.assertFalse(bike > foo)
+ self.assertFalse(bike >= foo)
+ # ... when classes are unrelated
+ class Bar(_Type): pass
+ bar = Bar('Bar', bike)
+ self.assertFalse(foo < bar)
+ self.assertFalse(foo <= bar)
+ self.assertFalse(foo > bar)
+ self.assertFalse(foo >= bar)
+ # goes both ways
+ self.assertFalse(bar < foo)
+ self.assertFalse(bar <= foo)
+ self.assertFalse(bar > foo)
+ self.assertFalse(bar >= foo)
+
class TestPredicate(unittest.TestCase):
def test_construction(self):
# domain must be a node
self.assertRaises(TypeError, Predicate, ns.bse.foo, 1234, None, True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Literal(ns.bsfs.Foo, None), None, True)
- # range must be None, a Literal, or a Node
+ # range must be a Literal, a Node, or the root Vertex
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), None, True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), 1234, True)
- self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), _Vertex(ns.bsfs.Foo, None), True)
+ self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), Vertex(ns.bsfs.Foo, None), True)
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), _Type(ns.bsfs.Foo, None), True)
class Foo(): pass
self.assertRaises(TypeError, Predicate, ns.bse.foo, None, Node(ns.bsfs.Node, None), Foo(), True)
@@ -138,82 +181,160 @@ class TestPredicate(unittest.TestCase):
n_root = Node(ns.bsfs.Node, None)
n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
n_tag = Node(ns.bsfs.Tag, Node(ns.bsfs.Tag, None))
- root = Predicate(
- uri=ns.bsfs.Predicate,
- parent=None,
+ root = ROOT_PREDICATE
+ tag = Predicate(
+ uri=ns.bse.tag,
+ parent=root,
domain=n_root,
- range=None,
+ range=n_tag,
unique=False,
)
# instance is equal to itself
- self.assertEqual(root, root)
- self.assertEqual(hash(root), hash(root))
+ self.assertEqual(tag, tag)
+ self.assertEqual(hash(tag), hash(tag))
# instance is equal to a clone
- self.assertEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, None, False))
- self.assertEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, None, False)))
+ self.assertEqual(tag, Predicate(ns.bse.tag, root, n_root, n_tag, False))
+ self.assertEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_tag, False)))
# equality respects uri
- self.assertNotEqual(root, Predicate(ns.bsfs.Alternative, None, n_root, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Alternative, None, n_root, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bsfs.Alternative, root, n_root, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bsfs.Alternative, root, n_root, n_tag, False)))
# equality respects parent
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, n_root, n_root, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, n_root, n_root, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, n_root, n_root, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, n_root, n_root, n_tag, False)))
# equality respects domain
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_ent, None, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_ent, None, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_ent, n_tag, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_ent, n_tag, False)))
# equality respects range
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, n_root, False))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, n_root, False)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_root, n_root, False))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_root, False)))
# equality respects unique
- self.assertNotEqual(root, Predicate(ns.bsfs.Predicate, None, n_root, None, True))
- self.assertNotEqual(hash(root), hash(Predicate(ns.bsfs.Predicate, None, n_root, None, True)))
+ self.assertNotEqual(tag, Predicate(ns.bse.tag, root, n_root, n_tag, True))
+ self.assertNotEqual(hash(tag), hash(Predicate(ns.bse.tag, root, n_root, n_tag, True)))
- def test_get_child(self):
+ def test_child(self):
n_root = Node(ns.bsfs.Node, None)
+ l_root = Literal(ns.bsfs.Literal, None)
n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
n_tag = Node(ns.bsfs.Tag, Node(ns.bsfs.Tag, None))
- root = Predicate(
- uri=ns.bsfs.Predicate,
- parent=None,
- domain=n_root,
- range=None,
- unique=False,
- )
+ root = ROOT_PREDICATE
tag = Predicate(
- uri=ns.bsfs.Entity,
+ uri=ns.bse.tag,
parent=root,
domain=n_ent,
range=n_tag,
unique=False,
)
+ # child returns Predicate
+ self.assertIsInstance(tag.child(ns.bse.foo), Predicate)
# uri is respected
- self.assertEqual(ns.bse.foo, tag.get_child(ns.bse.foo).uri)
+ self.assertEqual(ns.bse.foo, tag.child(ns.bse.foo).uri)
# domain is respected
dom = Node(ns.bsfs.Image, n_ent)
- self.assertEqual(dom, tag.get_child(ns.bse.foo, domain=dom).domain)
+ self.assertEqual(dom, tag.child(ns.bse.foo, domain=dom).domain)
# range is respected
rng = Node(ns.bsfs.Group, n_tag)
- self.assertEqual(rng, tag.get_child(ns.bse.foo, range=rng).range)
+ self.assertEqual(rng, tag.child(ns.bse.foo, range=rng).range)
# cannot set range to None
- self.assertEqual(n_tag, tag.get_child(ns.bse.foo, range=None).range)
+ self.assertEqual(n_tag, tag.child(ns.bse.foo, range=None).range)
# unique is respected
- self.assertTrue(tag.get_child(ns.bse.foo, unique=True).unique)
+ self.assertTrue(tag.child(ns.bse.foo, unique=True).unique)
+ # annotations are respected
+ self.assertDictEqual(tag.child(ns.bse.foo, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123,
+ })
# domain is inherited from parent
- self.assertEqual(n_ent, tag.get_child(ns.bse.foo).domain)
+ self.assertEqual(n_root, root.child(ns.bse.foo).domain)
+ self.assertEqual(n_ent, tag.child(ns.bse.foo).domain)
# range is inherited from parent
- self.assertEqual(n_tag, tag.get_child(ns.bse.foo).range)
+ self.assertEqual(ROOT_VERTEX, root.child(ns.bse.foo).range)
+ self.assertEqual(n_tag, tag.child(ns.bse.foo).range)
# uniqueness is inherited from parent
- self.assertFalse(tag.get_child(ns.bse.foo).unique)
+ self.assertFalse(tag.child(ns.bse.foo).unique)
# domain must be subtype of parent's domain
- self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, domain=n_root)
- self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, domain=Node(ns.bsfs.Image, n_root))
- # range cannot be None
- self.assertRaises(ValueError, root.get_child, ns.bse.foo)
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, domain=n_root)
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, domain=Node(ns.bsfs.Image, n_root))
# range must be subtype of parent's range
- self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, range=n_root)
- self.assertRaises(errors.ConsistencyError, tag.get_child, ns.bse.foo, range=Node(ns.bsfs.Image, n_root))
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, range=n_root)
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, range=Node(ns.bsfs.Image, n_root))
+ self.assertRaises(errors.ConsistencyError, tag.child, ns.bse.foo, range=Literal(ns.bsfs.Tag, l_root))
+ # range can be subtyped from ROOT_VERTEX to Node or Literal
+ self.assertEqual(n_root, root.child(ns.bse.foo, range=n_root).range)
+ self.assertEqual(l_root, root.child(ns.bse.foo, range=l_root).range)
+
+
+class TestFeature(unittest.TestCase):
+ def test_construction(self):
+ n_root = Node(ns.bsfs.Node, None)
+ l_root = Literal(ns.bsfs.Literal, None)
+ # dimension, dtype, and distance are respected
+ feat = Feature(ns.bsfs.Feature, None, 1234, ns.bsfs.float, ns.bsfs.euclidean)
+ self.assertEqual(1234, feat.dimension)
+ self.assertEqual(ns.bsfs.float, feat.dtype)
+ self.assertEqual(ns.bsfs.euclidean, feat.distance)
+
+ def test_equality(self):
+ n_ent = Node(ns.bsfs.Entity, Node(ns.bsfs.Node, None))
+ colors = Feature(
+ uri=ns.bse.colors,
+ parent=ROOT_FEATURE,
+ dimension=1234,
+ dtype=ns.bsfs.float,
+ distance=ns.bsfs.euclidean,
+ )
+ # instance is equal to itself
+ self.assertEqual(colors, colors)
+ self.assertEqual(hash(colors), hash(colors))
+ # instance is equal to a clone
+ self.assertEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.float, ns.bsfs.euclidean))
+ self.assertEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.float, ns.bsfs.euclidean)))
+ # equality respects dimension
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, 4321, ns.bsfs.float, ns.bsfs.euclidean))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, 4321, ns.bsfs.float, ns.bsfs.euclidean)))
+ # equality respects dtype
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.integer, ns.bsfs.euclidean))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.integer, ns.bsfs.euclidean)))
+ # equality respects distance
+ self.assertNotEqual(colors, Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.float, ns.bsfs.cosine))
+ self.assertNotEqual(hash(colors), hash(Feature(ns.bse.colors, ROOT_FEATURE, 1234, ns.bsfs.float, ns.bsfs.cosine)))
+
+ def test_child(self):
+ n_root = Node(ns.bsfs.Node, None)
+ n_ent = Node(ns.bsfs.Entity, n_root)
+ l_root = Literal(ns.bsfs.Literal, None)
+ colors = Feature(
+ uri=ns.bse.colors,
+ parent=ROOT_FEATURE,
+ dimension=1234,
+ dtype=ns.bsfs.float,
+ distance=ns.bsfs.euclidean,
+ )
+
+ # child returns Feature
+ self.assertIsInstance(colors.child(ns.bse.foo), Feature)
+ # uri is respected
+ self.assertEqual(ns.bse.foo, colors.child(ns.bse.foo).uri)
+ # dimension is respected
+ self.assertEqual(4321, colors.child(ns.bse.foo, dimension=4321).dimension)
+ # dtype is respected
+ self.assertEqual(ns.bsfs.integer, colors.child(ns.bse.foo, dtype=ns.bsfs.integer).dtype)
+ # distance is respected
+ self.assertEqual(ns.bsfs.cosine, colors.child(ns.bse.foo, distance=ns.bsfs.cosine).distance)
+ # annotations are respected
+ self.assertDictEqual(colors.child(ns.bse.foo, foo='bar', bar=123).annotations, {
+ 'foo': 'bar',
+ 'bar': 123,
+ })
+
+ # dimension is inherited from parent
+ self.assertEqual(1234, colors.child(ns.bse.foo).dimension)
+ # dtype is inherited from parent
+ self.assertEqual(ns.bsfs.float, colors.child(ns.bse.foo).dtype)
+ # distance is inherited from parent
+ self.assertEqual(ns.bsfs.euclidean, colors.child(ns.bse.foo).distance)
## main ##
@@ -222,4 +343,3 @@ if __name__ == '__main__':
unittest.main()
## EOF ##
-
diff --git a/test/triple_store/sparql/__init__.py b/test/triple_store/sparql/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/triple_store/sparql/__init__.py
diff --git a/test/triple_store/sparql/test_distance.py b/test/triple_store/sparql/test_distance.py
new file mode 100644
index 0000000..e95be5a
--- /dev/null
+++ b/test/triple_store/sparql/test_distance.py
@@ -0,0 +1,56 @@
+
+# imports
+import numpy as np
+import unittest
+
+# objects to test
+from bsfs.triple_store.sparql import distance
+
+
+## code ##
+
+class TestDistance(unittest.TestCase):
+
+ def test_euclid(self):
+ # self-distance is zero
+ self.assertEqual(distance.euclid([1,2,3,4], [1,2,3,4]), 0.0)
+ # accepts list-like arguments
+ self.assertAlmostEqual(distance.euclid([1,2,3,4], [2,3,4,5]), 2.0, 3)
+ self.assertAlmostEqual(distance.euclid((1,2,3,4), (2,3,4,5)), 2.0, 3)
+ # dimension can vary
+ self.assertAlmostEqual(distance.euclid([1,2,3], [2,3,4]), 1.732, 3)
+ self.assertAlmostEqual(distance.euclid([1,2,3,4,5], [2,3,4,5,6]), 2.236, 3)
+ # vector can be zero
+ self.assertAlmostEqual(distance.euclid([0,0,0], [1,2,3]), 3.742, 3)
+
+ def test_cosine(self):
+ # self-distance is zero
+ self.assertEqual(distance.cosine([1,2,3,4], [1,2,3,4]), 0.0)
+ # accepts list-like arguments
+ self.assertAlmostEqual(distance.cosine([1,2,3,4], [4,3,2,1]), 0.333, 3)
+ self.assertAlmostEqual(distance.cosine((1,2,3,4), (4,3,2,1)), 0.333, 3)
+ # dimension can vary
+ self.assertAlmostEqual(distance.cosine([1,2,3], [3,2,1]), 0.286, 3)
+ self.assertAlmostEqual(distance.cosine([1,2,3,4,5], [5,4,3,2,1]), 0.364, 3)
+ # vector can be zero
+ self.assertAlmostEqual(distance.cosine([0,0,0], [1,2,3]), 1.0, 3)
+
+ def test_manhatten(self):
+ # self-distance is zero
+ self.assertEqual(distance.manhatten([1,2,3,4], [1,2,3,4]), 0.0)
+ # accepts list-like arguments
+ self.assertAlmostEqual(distance.manhatten([1,2,3,4], [2,3,4,5]), 4.0, 3)
+ self.assertAlmostEqual(distance.manhatten((1,2,3,4), (2,3,4,5)), 4.0, 3)
+ # dimension can vary
+ self.assertAlmostEqual(distance.manhatten([1,2,3], [2,3,4]), 3.0, 3)
+ self.assertAlmostEqual(distance.manhatten([1,2,3,4,5], [2,3,4,5,6]), 5.0, 3)
+ # vector can be zero
+ self.assertAlmostEqual(distance.manhatten([0,0,0], [1,2,3]), 6.0, 3)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/triple_store/sparql/test_parse_fetch.py b/test/triple_store/sparql/test_parse_fetch.py
new file mode 100644
index 0000000..1d793e7
--- /dev/null
+++ b/test/triple_store/sparql/test_parse_fetch.py
@@ -0,0 +1,257 @@
+
+# imports
+import rdflib
+import unittest
+
+# bsie imports
+from bsfs import schema
+from bsfs.namespace import Namespace, ns
+from bsfs.query import ast
+from bsfs.utils import errors, URI
+
+# objects to test
+from bsfs.triple_store.sparql.parse_fetch import Fetch
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+ns.bst = ns.bsfs.Tag()
+ns.bsc = ns.bsfs.Collection()
+
+class TestParseFetch(unittest.TestCase):
+
+ def setUp(self):
+ self.schema = schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
+ prefix bsc: <https://schema.bsfs.io/core/Collection#>
+
+ # nodes
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:Collection rdfs:subClassOf bsfs:Node .
+
+ # literals
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ # predicates
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag .
+
+ bse:collection rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Collection .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string .
+
+ bse:rank rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer .
+
+ bst:main rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Entity .
+
+ bst:label rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range xsd:string .
+
+ bsc:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Collection ;
+ rdfs:range bsfs:Tag .
+
+ bsc:label rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Collection ;
+ rdfs:range xsd:string .
+
+ bsc:rating rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Collection ;
+ rdfs:range xsd:integer .
+
+ ''')
+
+ # graph to test queries
+ self.graph = rdflib.Graph()
+ # schema hierarchies
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Entity'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Collection'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Tag'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
+ # entities
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ # tags
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')))
+ # collections
+ self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Collection')))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Collection')))
+ # entity literals
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.rank), rdflib.Literal('1234', datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.filename), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string)))
+ #self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.rank), rdflib.Literal('4321', datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.filename), rdflib.Literal('filename_4321', datatype=rdflib.XSD.string)))
+ # tag literals
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.URIRef(ns.bst.label), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.URIRef(ns.bst.label), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string)))
+ # collection literals
+ self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(ns.bsc.label), rdflib.Literal('collection_label_1234', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(ns.bsc.rating), rdflib.Literal('1234', datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(ns.bsc.label), rdflib.Literal('collection_label_4321', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(ns.bsc.rating), rdflib.Literal('4321', datatype=rdflib.XSD.integer)))
+ # entity-tag links
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.tag), rdflib.URIRef('http://example.com/tag#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.tag), rdflib.URIRef('http://example.com/tag#4321')))
+ # entity-collection links
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.collection), rdflib.URIRef('http://example.com/collection#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.collection), rdflib.URIRef('http://example.com/collection#4321')))
+ # collection-tag links
+ self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(ns.bsc.tag), rdflib.URIRef('http://example.com/tag#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(ns.bsc.tag), rdflib.URIRef('http://example.com/tag#4321')))
+ # tag-entity links # NOTE: cross-over
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.URIRef(ns.bst.main), rdflib.URIRef('http://example.com/entity#4321')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.URIRef(ns.bst.main), rdflib.URIRef('http://example.com/entity#1234')))
+
+ # default parser
+ self.parser = Fetch(self.schema)
+ self.ent = self.schema.node(ns.bsfs.Entity)
+
+
+ def test_call(self):
+ # NOTE: The individual ast components are considered in the respective tests. Here, we test __call__ specifics.
+
+ # __call__ requires a valid root type
+ self.assertRaises(errors.BackendError, self.parser, self.schema.literal(ns.bsfs.Literal), ast.fetch.This('this'))
+ self.assertRaises(errors.ConsistencyError, self.parser, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), ast.fetch.This('this'))
+ # __call__ requires a parseable root
+ self.assertRaises(errors.BackendError, self.parser, self.ent, ast.filter.FilterExpression())
+ # __call__ returns an executable query
+ q = self.parser(self.ent, ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bst.label, 'label')))
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string)),
+ })
+
+
+ def test_routing(self):
+ self.assertRaises(errors.BackendError, self.parser._parse_fetch_expression, self.ent, ast.fetch.FetchExpression(), '?head')
+
+
+ def test_all(self):
+ # multiple values query
+ q = self.parser(self.ent, ast.fetch.All(
+ ast.fetch.Value(ns.bse.filename, name='filename'),
+ ast.fetch.Value(ns.bse.rank, name='rank')),
+ )
+ self.assertSetEqual(set(q.names), {'filename', 'rank'})
+ if q.names == ('filename', 'rank'):
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('filename_4321', datatype=rdflib.XSD.string), None),
+ })
+ else:
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('1234', datatype=rdflib.XSD.integer), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/entity#4321'), None, rdflib.Literal('filename_4321', datatype=rdflib.XSD.string)),
+ })
+ # mixed values and node query
+ q = self.parser(self.ent, ast.fetch.All(
+ ast.fetch.Value(ns.bse.filename, name='filename'),
+ ast.fetch.Node(ns.bse.tag, name='tag'),
+ ))
+ self.assertSetEqual(set(q.names), {'filename', 'tag'})
+ if q.names == ('filename', 'tag'):
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string), rdflib.URIRef('http://example.com/tag#1234')),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('filename_4321', datatype=rdflib.XSD.string), rdflib.URIRef('http://example.com/tag#4321')),
+ })
+ else:
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef('http://example.com/tag#1234'), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef('http://example.com/tag#4321'), rdflib.Literal('filename_4321', datatype=rdflib.XSD.string)),
+ })
+ # multiple values and second hop
+ q = self.parser(self.ent, ast.fetch.Fetch(ns.bse.tag, ast.fetch.All(
+ ast.fetch.This(name='tag'),
+ ast.fetch.Value(ns.bst.label, name='label'),
+ )))
+ self.assertSetEqual(set(q.names), {'tag', 'label'})
+ if q.names == ('tag', 'label'):
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef('http://example.com/tag#1234'), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef('http://example.com/tag#4321'), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string)),
+ })
+ else:
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string), rdflib.URIRef('http://example.com/tag#1234')),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string), rdflib.URIRef('http://example.com/tag#4321')),
+ })
+
+
+
+ def test_fetch(self):
+ # two-hop query
+ q = self.parser(self.ent, ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bst.label, 'tag_label')))
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string)),
+ })
+ # three-hop-query
+ q = self.parser(self.ent, ast.fetch.Fetch(ns.bse.tag, ast.fetch.Fetch(ns.bst.main, ast.fetch.Value(ns.bse.rank, 'entity_rank'))))
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), None),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ })
+
+
+ def test_node(self):
+ # cannot use the internal hop name
+ self.assertRaises(errors.BackendError, self.parser, self.ent, ast.fetch.Node(ns.bse.tag, self.parser.ngen.prefix[1:] + '123'))
+ # a simple Node statement
+ q = self.parser(self.ent, ast.fetch.Node(ns.bse.tag, 'tag'))
+ self.assertSetEqual(set(q.names), {'tag'})
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef('http://example.com/tag#1234')),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef('http://example.com/tag#4321')),
+ })
+
+
+ def test_value(self):
+ # cannot use the internal hop name
+ self.assertRaises(errors.BackendError, self.parser, self.schema.node(ns.bsfs.Entity), ast.fetch.Value(ns.bse.filename, self.parser.ngen.prefix[1:] + '123'))
+ # a simple Value statement
+ q = self.parser(self.ent, ast.fetch.Value(ns.bse.filename, 'filename'))
+ self.assertSetEqual(set(q.names), {'filename'})
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('filename_4321', datatype=rdflib.XSD.string)),
+ })
+
+
+ def test_this(self):
+ # cannot use the internal hop name
+ self.assertRaises(errors.BackendError, self.parser, self.ent, ast.fetch.This(self.parser.ngen.prefix[1:] + '123'))
+ # a simple This statement
+ self.assertEqual(self.parser._this(self.ent, ast.fetch.This('this'), '?head'),
+ ({('?head', 'this')}, ''))
+ q = self.parser(self.ent, ast.fetch.This('this'))
+ self.assertSetEqual(set(q(self.graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef('http://example.com/entity#1234')),
+ (rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef('http://example.com/entity#4321')),
+ })
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/triple_store/sparql/test_parse_filter.py b/test/triple_store/sparql/test_parse_filter.py
new file mode 100644
index 0000000..a45f2ef
--- /dev/null
+++ b/test/triple_store/sparql/test_parse_filter.py
@@ -0,0 +1,777 @@
+
+# imports
+import rdflib
+import unittest
+
+# bsie imports
+from bsfs import schema as bsc
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.utils import errors
+
+# objects to test
+from bsfs.triple_store.sparql.parse_filter import Filter
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+
+class TestParseFilter(unittest.TestCase):
+ def setUp(self):
+ # schema
+ self.schema = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsd: <https://schema.bsfs.io/core/distance#>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
+ bsfs:URI rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "4"^^xsd:integer ;
+ bsfs:dtype xsd:integer ;
+ bsfs:distance bsd:euclidean .
+
+ bse:colors rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Colors .
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:string ;
+ 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:buddy rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:representative rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Image ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:iso rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Image ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ ''')
+
+ # graph to test queries
+ self.graph = rdflib.Graph()
+ # schema hierarchies
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Entity'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Image'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Tag'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
+ # entities
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ # tags
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')))
+ # images
+ self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Image')))
+ self.graph.add((rdflib.URIRef('http://example.com/image#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Image')))
+ # node comments
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('Me, Myself, and I', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('hello world', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('hello world', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('Me, Myself, and I', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('Me, Myself, and I', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('4321', datatype=rdflib.XSD.string)))
+ # entity filesizes
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.filesize), rdflib.Literal(1234, datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.filesize), rdflib.Literal(4321, datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.URIRef(ns.bse.filesize), rdflib.Literal(1234, datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/image#4321'), rdflib.URIRef(ns.bse.filesize), rdflib.Literal(4321, datatype=rdflib.XSD.integer)))
+ # entity tags
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.tag), rdflib.URIRef('http://example.com/tag#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.tag), rdflib.URIRef('http://example.com/tag#4321')))
+ self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.URIRef(ns.bse.tag), rdflib.URIRef('http://example.com/tag#1234')))
+ # tag representatives
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.URIRef(ns.bse.representative), rdflib.URIRef('http://example.com/image#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.URIRef(ns.bse.representative), rdflib.URIRef('http://example.com/image#4321')))
+ # entity buddies
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.buddy), rdflib.URIRef('http://example.com/image#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.buddy), rdflib.URIRef('http://example.com/image#4321')))
+ # image iso
+ self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.URIRef(ns.bse.iso), rdflib.Literal(1234, datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/image#4321'), rdflib.URIRef(ns.bse.iso), rdflib.Literal(4321, datatype=rdflib.XSD.integer)))
+ # color features
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.colors), rdflib.Literal([1,2,3,4], datatype=rdflib.URIRef(ns.bsfs.Colors))))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.colors), rdflib.Literal([4,3,2,1], datatype=rdflib.URIRef(ns.bsfs.Colors))))
+ self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.URIRef(ns.bse.colors), rdflib.Literal([3,4,2,1], datatype=rdflib.URIRef(ns.bsfs.Colors))))
+
+ # parser instance
+ self.parser = Filter(self.graph, self.schema)
+
+
+ def test_routing(self):
+ self.assertRaises(errors.BackendError, self.parser._parse_filter_expression, '1234', None, '')
+ self.assertRaises(errors.BackendError, self.parser._parse_predicate_expression, '1234', None)
+
+ def test_call(self):
+ # NOTE: The individual ast components are considered in the respective tests. Here, we test __call__ specifics.
+
+ # __call__ requires a valid root type
+ self.assertRaises(errors.BackendError, self.parser, self.schema.literal(ns.bsfs.Literal), None)
+ self.assertRaises(errors.ConsistencyError, self.parser, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), None)
+ # __call__ requires a parseable root
+ self.assertRaises(errors.BackendError, self.parser, self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression())
+ # __call__ returns an executable query
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Is('http://example.com/entity#5678')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, {'http://example.com/entity#1234'})
+ # root is optional
+ q = self.parser(self.schema.node(ns.bsfs.Entity))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321', 'http://example.com/image#1234', 'http://example.com/image#4321'})
+ q = self.parser(self.schema.node(ns.bsfs.Tag))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/tag#1234', 'http://example.com/tag#4321'})
+
+
+ def test_is(self):
+ # _is requires a node
+ self.assertRaises(errors.BackendError, self.parser._is, self.schema.literal(ns.bsfs.Literal), ast.filter.Is('http://example.com/entity#1234'), '?ent')
+ # _is requires a serializable guid
+ self.assertRaises(ValueError, self.parser._is, self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/entity#foo and bar'), '?ent')
+ # a single Is statement
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/entity#1234'))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234'})
+ # an aggregate of Is statements
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Is('http://example.com/entity#4321'),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321'})
+ # combined with other filters
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Is('http://example.com/entity#4321'),
+ ),
+ ast.filter.Any(ns.bse.comment,
+ ast.filter.Equals('Me, Myself, and I')
+ ),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234'})
+ # as argument of Any/All
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/tag#1234')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+
+
+ def test_equals(self):
+ # _equals requires a literal
+ self.assertRaises(errors.BackendError, self.parser._equals, self.schema.node(ns.bsfs.Entity), ast.filter.Equals('hello world'), '?ent')
+ # a single Equals statement
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.Equals('hello world')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321'})
+ # a single Equals statement that includes subtypes
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # an Equals statement on an integer
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.filesize, ast.filter.Equals(4321)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#4321'})
+
+
+ def test_substring(self):
+ # _substring requires a literal
+ self.assertRaises(errors.BackendError, self.parser._substring, self.schema.node(ns.bsfs.Entity), ast.filter.Substring('hello world'), '?ent')
+ # a single Substring statement
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.Substring('hello')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.Substring('lo wo')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321'})
+ # a single Substring statement that includes subtypes
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.Substring('Myself')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # an Substring statement on an integer
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.filesize, ast.filter.Substring('32')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#4321'})
+
+
+ def test_starts_with(self):
+ # _starts_with requires a literal
+ self.assertRaises(errors.BackendError, self.parser._starts_with, self.schema.node(ns.bsfs.Entity), ast.filter.StartsWith('hello world'), '?ent')
+ # a single StartsWith statement
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('hello')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321'})
+ # a single StartsWith statement that includes subtypes
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('Me, Mys')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # an StartsWith statement on an integer
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.filesize, ast.filter.StartsWith(432)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#4321'})
+
+
+ def test_ends_with(self):
+ # _ends_with requires a literal
+ self.assertRaises(errors.BackendError, self.parser._ends_with, self.schema.node(ns.bsfs.Entity), ast.filter.EndsWith('hello world'), '?ent')
+ # a single EndsWith statement
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.EndsWith('orld')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321'})
+ # a single EndsWith statement that includes subtypes
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.EndsWith('and I')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # an EndsWith statement on an integer
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.filesize, ast.filter.EndsWith(321)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#4321'})
+
+
+ def test_less_than(self):
+ # _less_than requires a literal
+ self.assertRaises(errors.BackendError, self.parser._less_than, self.schema.node(ns.bsfs.Entity), ast.filter.LessThan(2000), '?ent')
+ # a single LessThan statement
+ q = self.parser(self.schema.node(ns.bsfs.Image), ast.filter.Any(ns.bse.iso, ast.filter.LessThan(2000)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#1234'})
+ # _less_than respects boundary
+ q = self.parser(self.schema.node(ns.bsfs.Image), ast.filter.Any(ns.bse.iso, ast.filter.LessThan(1234, strict=True)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+ q = self.parser(self.schema.node(ns.bsfs.Image), ast.filter.Any(ns.bse.iso, ast.filter.LessThan(1234, strict=False)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#1234'})
+ # a single LessThan statement that includes subtypes
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.filesize, ast.filter.LessThan(2000)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # an LessThan statement on a string
+ # always negative; note that http://example.com/tag#4321 is also not returned although its comment is a pure number
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.LessThan(10_000)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+
+
+ def test_greater_than(self):
+ # _greater_than requires a literal
+ self.assertRaises(errors.BackendError, self.parser._greater_than, self.schema.node(ns.bsfs.Entity), ast.filter.GreaterThan(2000), '?ent')
+ # a single GreaterThan statement
+ q = self.parser(self.schema.node(ns.bsfs.Image), ast.filter.Any(ns.bse.iso, ast.filter.GreaterThan(2000)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#4321'})
+ # _greater_than respects boundary
+ q = self.parser(self.schema.node(ns.bsfs.Image), ast.filter.Any(ns.bse.iso, ast.filter.GreaterThan(4321, strict=True)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+ q = self.parser(self.schema.node(ns.bsfs.Image), ast.filter.Any(ns.bse.iso, ast.filter.GreaterThan(4321, strict=False)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#4321'})
+ # a single GreaterThan statement that includes subtypes
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.filesize, ast.filter.GreaterThan(2000)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#4321'})
+ # an GreaterThan statement on a string
+ # always positive
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.comment, ast.filter.GreaterThan(0)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321', 'http://example.com/image#1234'})
+
+
+ def test_and(self):
+ # And childs have to match the node type
+ self.assertRaises(errors.BackendError, self.parser,
+ self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.StartsWith('hello'),
+ ast.filter.EndsWith('world'),
+ ))
+ # no child produces an empty query
+ self.assertEqual(self.parser._and(
+ self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(), '?ent'), '')
+ # And can mix different conditions
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234'})
+ # all conditions have to match
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Is('http://example.com/entity#4321'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(4321)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('foobar')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+ # And can be nested
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.And(
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ ),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234'})
+
+
+ def test_or(self):
+ # Or childs have to match the node type
+ self.assertRaises(errors.BackendError, self.parser,
+ self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.StartsWith('hello'),
+ ast.filter.EndsWith('world'),
+ ))
+ # no child produces an empty query
+ self.assertEqual(self.parser._and(
+ self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(), '?ent'), '')
+ # Or can mix different conditions
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(4321)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234', 'http://example.com/entity#4321', 'http://example.com/image#4321'})
+ # at least one condition has to match
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#5678'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(8765)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('foobar')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(8765)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('foobar')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#5678'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(4321)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('foobar')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#4321'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#5678'),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(8765)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # Or can be nested
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Or(
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(4321)),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ ),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234', 'http://example.com/entity#4321', 'http://example.com/image#4321'})
+
+
+
+ def test_any(self):
+ # _any requires a node
+ self.assertRaises(errors.BackendError, self.parser._any,
+ self.schema.literal(ns.bsfs.Literal),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234)), '?ent')
+ # node type must match predicate's domain
+ self.assertRaises(errors.ConsistencyError, self.parser._any,
+ self.schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234)), '?ent')
+ # predicate must be valid
+ self.assertRaises(errors.ConsistencyError, self.parser._any,
+ self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.invalid, ast.filter.Equals(1234)), '?ent')
+ # _any returns a valid query
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # _any can be nested
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.tag,
+ ast.filter.Any(ns.bse.representative,
+ ast.filter.Is('http://example.com/image#1234'))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+
+
+ def test_all(self):
+ # All requires a Node
+ self.assertRaises(errors.BackendError, self.parser._all, self.schema.literal(ns.bsfs.Literal), None, '')
+ # All Nodes
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.All(ns.bse.tag, ast.filter.Is('http://example.com/tag#1234')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # All values
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.All(ns.bse.comment, ast.filter.Equals('hello world')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321'})
+ # All on value within Or branch
+ # entity#1234 is selected because all of its comments are in ("hello world", "Me, Myself, and I")
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.All(ns.bse.comment, ast.filter.Or(
+ ast.filter.Equals('hello world'),
+ ast.filter.Equals('Me, Myself, and I'))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321', 'http://example.com/image#1234'})
+ # All requires at least one predicate/value
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.All(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#1234'})
+ # All within a statement
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.All(ns.bse.tag, ast.filter.Is('http://example.com/tag#1234')), # entity#1234, image#1234
+ ast.filter.All(ns.bse.comment, ast.filter.Or( # entity#1234, entity#4321, image#1234
+ ast.filter.Equals('hello world'),
+ ast.filter.Equals('Me, Myself, and I'),
+ ))
+ )
+ )
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ # All with reversed Predicate
+ q = self.parser(self.schema.node(ns.bsfs.Tag),
+ ast.filter.All(ast.filter.Predicate(ns.bse.tag, reverse=True), ast.filter.Is('http://example.com/entity#4321')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/tag#4321'})
+ # All with multiple predicates
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.All(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy), # entity#1234 (tag:tag#1234), entity#1234 (buddy:image#1234), image#1234(tag:tag#1234)
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')))) # entity#1234, image#1234, tag#1234
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+
+
+
+ def test_not(self):
+ # Not applies on conditions
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(ast.filter.Is('http://example.com/entity#1234')))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#1234', 'http://example.com/entity#4321', 'http://example.com/image#4321'})
+ # Not applies on conditions within branches
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.comment, ast.filter.Not(ast.filter.Equals('Me, Myself, and I'))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321'})
+ # Not applies on branches
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I'))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#4321'})
+ # Double Not cancel each other
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(ast.filter.Not(ast.filter.Is('http://example.com/entity#1234'))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234'})
+ # Not works within aggregation (and)
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Not(ast.filter.Is('http://example.com/entity#1234')),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('hello world')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321'})
+ # Not works within aggregation (or)
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(
+ ast.filter.Not(ast.filter.Is('http://example.com/entity#1234')),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321', 'http://example.com/image#1234', 'http://example.com/image#4321'})
+ # Not works outside aggregation (and)
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(
+ ast.filter.And(
+ ast.filter.Is('http://example.com/entity#1234'),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('hello world')),
+ )))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#1234', 'http://example.com/image#4321'})
+ # Not works outside aggregation (or)
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/entity#4321'),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('Me, Myself, and I')),
+ )))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#4321'})
+ # Not mixed with branch, aggregation, id, and value
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(
+ ast.filter.Not( # image#1234, image#4321
+ ast.filter.Or( # entity#4321, entity#1234
+ ast.filter.Is('http://example.com/entity#4321'),
+ ast.filter.Any(ns.bse.comment, ast.filter.Equals('hello world')),
+ )
+ ),
+ ast.filter.Any(ns.bse.comment, ast.filter.Not(ast.filter.Equals('foobar'))), # entity#1234, entity#4321, image#1234
+ ))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#1234'})
+
+
+ def test_has(self):
+ # Has requires Node
+ self.assertRaises(errors.BackendError, self.parser._has, self.schema.literal(ns.bsfs.Literal), None, '')
+ # Has with GreaterThan constraint
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.comment, ast.filter.GreaterThan(0)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321', 'http://example.com/image#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.comment, ast.filter.GreaterThan(1)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234'})
+ # Has with Equals constraint
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.comment, 1))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#1234'})
+ # Has with LessThan constraint
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.comment, ast.filter.LessThan(2)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#1234', 'http://example.com/image#4321'})
+ # Has with multiple constraints
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('extra1', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('extra2', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('extra3', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('extra4', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('extra5', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('extra1', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('extra2', datatype=rdflib.XSD.string)))
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Has(ns.bse.comment,
+ ast.filter.And(ast.filter.GreaterThan(1), ast.filter.LessThan(5))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321'})
+ # Has with OneOf predicate
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Has(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy),
+ ast.filter.GreaterThan(1)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321'})
+ # Has with reversed predicate
+ q = self.parser(self.schema.node(ns.bsfs.Tag), ast.filter.Has(ast.filter.Predicate(ns.bse.tag, reverse=True),
+ ast.filter.GreaterThan(1)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/tag#1234'})
+
+
+ def test_distance(self):
+ # node colors distance to [2,4,3,1]
+ # entity#1234 [1,2,3,4] 3.742
+ # entity#4321 [4,3,2,1] 2.449
+ # image#1234 [3,4,2,1] 1.414
+
+ # _distance expects a feature
+ self.assertRaises(errors.BackendError, self.parser._distance, self.schema.node(ns.bsfs.Entity), ast.filter.Distance([1,2,3,4], 1), '')
+ # reference must have the correct dimension
+ self.assertRaises(errors.ConsistencyError, self.parser._distance, self.schema.literal(ns.bsfs.Colors), ast.filter.Distance([1,2,3], 1), '')
+ self.assertRaises(errors.ConsistencyError, self.parser._distance, self.schema.literal(ns.bsfs.Colors), ast.filter.Distance([1,2,3,4,5], 1), '')
+ # _distance respects threshold
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([2,4,3,1], 4)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/entity#4321', 'http://example.com/image#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([2,4,3,1], 3)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#4321', 'http://example.com/image#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([2,4,3,1], 2)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/image#1234'})
+ # result set can be empty
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([2,4,3,1], 1)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+ # _distance respects strict
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([1,2,3,4], 0, False)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Any(ns.bse.colors, ast.filter.Distance([1,2,3,4], 0, True)))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)}, set())
+
+ def test_one_of(self):
+ # _one_of expects a node
+ self.assertRaises(errors.BackendError, self.parser._one_of,
+ self.schema.literal(ns.bsfs.Literal),
+ ast.filter.OneOf(ast.filter.Predicate(ns.bse.filesize)))
+ # invalid predicate for node type raises an error
+ self.assertRaises(errors.ConsistencyError, self.parser._one_of,
+ self.schema.node(ns.bsfs.Node),
+ ast.filter.OneOf(ast.filter.Predicate(ns.bse.filesize)))
+ self.assertRaises(errors.ConsistencyError, self.parser,
+ self.schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ast.filter.OneOf(ast.filter.Predicate(ns.bse.filesize)), ast.filter.Equals(1234)))
+ self.assertRaises(errors.BackendError, self.parser._one_of,
+ self.schema.node(ns.bsfs.Node),
+ ast.filter.OneOf(ast.filter.Predicate(ns.bsfs.Predicate)))
+ # invalid predicate combinations raise an error
+ self.assertRaises(errors.ConsistencyError, self.parser._one_of,
+ self.schema.node(ns.bsfs.Node),
+ ast.filter.OneOf(
+ ast.filter.Predicate(ns.bse.filesize),
+ ast.filter.Predicate(ns.bse.representative)))
+ # _one_of returns the URI and range
+ q = self.parser._one_of(self.schema.node(ns.bsfs.Image),
+ ast.filter.OneOf(
+ ast.filter.Predicate(ns.bse.iso),
+ ast.filter.Predicate(ns.bse.filesize)))
+ self.assertTrue(q[0] == f'<{ns.bse.iso}>|<{ns.bse.filesize}>' or q[0] == f'<{ns.bse.filesize}>|<{ns.bse.iso}>')
+ self.assertEqual(q[1], self.schema.literal(ns.xsd.integer))
+ # OneOf can be nested
+ q = self.parser._one_of(self.schema.node(ns.bsfs.Image),
+ ast.filter.OneOf(
+ ast.filter.Predicate(ns.bse.iso),
+ ast.filter.OneOf(
+ ast.filter.Predicate(ns.bse.filesize))))
+ self.assertTrue(q[0] == f'<{ns.bse.iso}>|<{ns.bse.filesize}>' or q[0] == f'<{ns.bse.filesize}>|<{ns.bse.iso}>')
+ self.assertEqual(q[1], self.schema.literal(ns.xsd.integer))
+ # _one_of returns the most generic range
+ q = self.parser._one_of(self.schema.node(ns.bsfs.Entity),
+ ast.filter.OneOf(
+ ast.filter.Predicate(ns.bse.tag),
+ ast.filter.Predicate(ns.bse.buddy)))
+ self.assertTrue(q[0] == f'<{ns.bse.tag}>|<{ns.bse.buddy}>' or q[0] == f'<{ns.bse.buddy}>|<{ns.bse.tag}>')
+ self.assertEqual(q[1], self.schema.node(ns.bsfs.Node))
+ # domains must match the given type
+ self.assertRaises(errors.ConsistencyError, self.parser,
+ self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.filesize),
+ ast.filter.Equals(1234))))
+ # ranges must have the same type (Node/Literal)
+ self.assertRaises(errors.ConsistencyError, self.parser,
+ self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.tag, ns.bse.filesize),
+ ast.filter.Equals(1234)))
+ # ranges must be related
+ self.assertRaises(errors.ConsistencyError, self.parser,
+ self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.comment, ns.bse.filesize),
+ ast.filter.Equals(1234)))
+ # integration: _one_of returns a valid sparql query
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.comment),
+ ast.filter.Equals('Me, Myself, and I'))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+
+
+ def test_predicate(self):
+ # predicate cannot be the root predicate (ns.bsfs.Predicate)
+ self.assertRaises(errors.BackendError, self.parser._predicate, self.schema.node(ns.bsfs.Node), ast.filter.Predicate(ns.bsfs.Predicate))
+ # _predicate expects a node
+ self.assertRaises(errors.BackendError, self.parser._predicate,
+ self.schema.literal(ns.bsfs.Literal),
+ ast.filter.Predicate(ns.bse.filesize))
+ # invalid predicate for node type raises an error
+ self.assertRaises(errors.ConsistencyError, self.parser._predicate,
+ self.schema.node(ns.bsfs.Node),
+ ast.filter.Predicate(ns.bse.filesize))
+ self.assertRaises(errors.ConsistencyError, self.parser,
+ self.schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.filesize), ast.filter.Equals(1234)))
+ # _predicate returns the URI and range
+ self.assertEqual(self.parser._predicate(self.schema.node(ns.bsfs.Entity), ast.filter.Predicate(ns.bse.filesize)),
+ (f'<{ns.bse.filesize}>', self.schema.literal(ns.xsd.integer)))
+ self.assertEqual(self.parser._predicate(self.schema.node(ns.bsfs.Entity), ast.filter.Predicate(ns.bse.tag)),
+ (f'<{ns.bse.tag}>', self.schema.node(ns.bsfs.Tag)))
+ # _predicate respects reverse flag
+ self.assertEqual(self.parser._predicate(self.schema.node(ns.bsfs.Tag), ast.filter.Predicate(ns.bse.tag, reverse=True)),
+ ('^<' + ns.bse.tag + '>', self.schema.node(ns.bsfs.Entity)))
+ # integration: _predicate returns a valid sparql query
+ q = self.parser(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.tag,
+ ast.filter.Any(ns.bse.representative,
+ ast.filter.Any(ns.bse.filesize,
+ ast.filter.Equals(1234)))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/entity#1234', 'http://example.com/image#1234'})
+ q = self.parser(self.schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.tag, reverse=True),
+ ast.filter.Any(ns.bse.filesize,
+ ast.filter.LessThan(2000))))
+ self.assertSetEqual({str(guid) for guid, in q(self.graph)},
+ {'http://example.com/tag#1234'})
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/triple_store/test_sparql.py b/test/triple_store/sparql/test_sparql.py
index 8d98749..a7e7d37 100644
--- a/test/triple_store/test_sparql.py
+++ b/test/triple_store/sparql/test_sparql.py
@@ -1,38 +1,39 @@
-"""
-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 import schema as bsc
from bsfs.namespace import ns
+from bsfs.query import ast
from bsfs.utils import errors, URI
# objects to test
-from bsfs.triple_store.sparql import SparqlStore
+from bsfs.triple_store.sparql.sparql import SparqlStore
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestSparqlStore(unittest.TestCase):
def setUp(self):
- self.schema = _schema.Schema.from_string('''
+ self.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
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 .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ bsl:BinaryBlob rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
# non-unique literal
bse:comment rdfs:subClassOf bsfs:Predicate ;
@@ -58,7 +59,30 @@ class TestSparqlStore(unittest.TestCase):
rdfs:range bsfs:User ;
bsfs:unique "true"^^xsd:boolean .
+ # binary range
+ bse:asset rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsl:BinaryBlob .
+
''')
+ self.schema_triples = {
+ # schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Entity), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.string), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)),
+ (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)),
+ (rdflib.URIRef(ns.bse.comment), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.author), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.asset), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ }
def test_essentials(self):
store = SparqlStore.Open()
@@ -77,10 +101,10 @@ class TestSparqlStore(unittest.TestCase):
def test__has_type(self):
# setup store
store = SparqlStore.Open()
- store.schema = _schema.Schema.from_string('''
+ store.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Document rdfs:subClassOf bsfs:Entity .
@@ -95,7 +119,7 @@ class TestSparqlStore(unittest.TestCase):
store.create(store.schema.node(ns.bsfs.PDF), {URI('http://example.com/me/pdf#1234')})
# node_type must be in the schema
- self.assertRaises(errors.ConsistencyError, store._has_type, URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Node).get_child(ns.bsfs.invalid))
+ self.assertRaises(errors.ConsistencyError, store._has_type, URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Node).child(ns.bsfs.invalid))
# returns False on inexistent nodes
self.assertFalse(store._has_type(URI('http://example.com/me/entity#4321'), store.schema.node(ns.bsfs.Entity)))
@@ -155,7 +179,7 @@ class TestSparqlStore(unittest.TestCase):
store.set(curr.node(ns.bsfs.Entity), ent_ids, p_author,
{URI('http://example.com/me')})
# check instances
- instances = {
+ instances = self.schema_triples | {
# 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)),
@@ -182,13 +206,13 @@ class TestSparqlStore(unittest.TestCase):
self.assertSetEqual(set(store._graph), instances)
# add some classes to the schema
- curr = curr + _schema.Schema.from_string('''
+ curr = curr + bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
- prefix bst: <http://bsfs.ai/schema/Tag#>
- prefix bsc: <http://bsfs.ai/schema/Collection#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
+ prefix bsc: <https://schema.bsfs.io/core/Collection#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
@@ -228,13 +252,22 @@ class TestSparqlStore(unittest.TestCase):
store.schema = curr
self.assertEqual(store.schema, curr)
# instances have not changed
- self.assertSetEqual(set(store._graph), instances)
+ self.assertSetEqual(set(store._graph), instances | {
+ # schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Collection), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.partOf), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#usedIn'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Collection#tag'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ })
# 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')
+ p_usedIn = curr.predicate('https://schema.bsfs.io/core/Tag#usedIn')
+ p_ctag = curr.predicate('https://schema.bsfs.io/core/Collection#tag')
+ p_principal = curr.predicate('https://schema.bsfs.io/core/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})
@@ -248,6 +281,14 @@ class TestSparqlStore(unittest.TestCase):
{URI('http://example.com/me/collection#1234')})
# new instances are now in the graph
self.assertSetEqual(set(store._graph), instances | {
+ # same old schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Collection), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.partOf), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#usedIn'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Collection#tag'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
# 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)),
@@ -268,19 +309,21 @@ class TestSparqlStore(unittest.TestCase):
# remove some classes from the schema
- curr = _schema.Schema.from_string('''
+ curr = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
- prefix bst: <http://bsfs.ai/schema/Tag#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
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 .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
bse:filesize rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -316,6 +359,21 @@ class TestSparqlStore(unittest.TestCase):
self.assertEqual(store.schema, curr)
# instances of old classes were removed
self.assertSetEqual(set(store._graph), {
+ # schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Entity), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)),
+ (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)),
+ (rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
# 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)),
@@ -342,12 +400,31 @@ class TestSparqlStore(unittest.TestCase):
class Foo(): pass
self.assertRaises(TypeError, setattr, store, 'schema', Foo())
+ # cannot define features w/o known distance function
+ invalid = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "4"^^xsd:integer ;
+ bsfs:distance bsfs:foobar .
+
+ ''')
+ self.assertRaises(errors.UnsupportedError, setattr, store, 'schema', invalid)
+
# cannot migrate to incompatible schema
- invalid = _schema.Schema.from_string('''
+ invalid = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Entity . # inconsistent with previous tag definition
@@ -359,11 +436,11 @@ class TestSparqlStore(unittest.TestCase):
''')
self.assertRaises(errors.ConsistencyError, setattr, store, 'schema', invalid)
- invalid = _schema.Schema.from_string('''
+ invalid = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:User rdfs:subClassOf bsfs:Node .
@@ -390,7 +467,7 @@ class TestSparqlStore(unittest.TestCase):
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 = {
+ instances = self.schema_triples | {
# 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)),
@@ -416,7 +493,7 @@ class TestSparqlStore(unittest.TestCase):
# rollback undoes previous changes
store.rollback()
- self.assertSetEqual(set(store._graph), set())
+ self.assertSetEqual(set(store._graph), self.schema_triples)
# add some data once more
store.create(ent_type, ent_ids)
@@ -455,6 +532,109 @@ class TestSparqlStore(unittest.TestCase):
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
})
+ def test_get(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ ent_type = self.schema.node(ns.bsfs.Entity)
+ tag_type = self.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')}
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.set(ent_type, ent_ids, self.schema.predicate(ns.bse.tag), tag_ids)
+ store.set(ent_type, {URI('http://example.com/me/entity#1234')}, self.schema.predicate(ns.bse.filesize), {1234})
+ store.set(ent_type, {URI('http://example.com/me/entity#4321')}, self.schema.predicate(ns.bse.filesize), {4321})
+ # node_type must be in the schema
+ self.assertRaises(errors.ConsistencyError, set, store.get(self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), ast.filter.IsIn(ent_ids)))
+ # query must be a filter expression
+ class Foo(): pass
+ self.assertRaises(TypeError, set, store.get(ent_type, 1234))
+ self.assertRaises(TypeError, set, store.get(ent_type, '1234'))
+ self.assertRaises(TypeError, set, store.get(ent_type, Foo()))
+ # run some queries
+ self.assertSetEqual(set(store.get(tag_type, ast.filter.IsIn(tag_ids))), tag_ids)
+ self.assertSetEqual(set(store.get(ent_type, ast.filter.Any(ns.bse.tag, ast.filter.IsIn(tag_ids)))), ent_ids)
+ self.assertSetEqual(set(store.get(ent_type, ast.filter.IsIn(tag_ids))), set())
+ # invalid queries raise error
+ self.assertRaises(errors.ConsistencyError, set, store.get(tag_type, ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234))))
+ self.assertRaises(errors.BackendError, set, store.get(ent_type, ast.filter.Equals('http://example.com/me/entity#1234')))
+ # run some more complex query
+ q = store.get(tag_type, ast.filter.Any(ast.filter.Predicate(ns.bse.tag, reverse=True),
+ ast.filter.Any(ns.bse.filesize,
+ ast.filter.LessThan(2000))))
+ self.assertSetEqual(set(q), tag_ids)
+
+
+ def test_fetch(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ # add instances
+ ent_type = self.schema.node(ns.bsfs.Entity)
+ tag_type = self.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')}
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.set(ent_type, ent_ids, self.schema.predicate(ns.bse.tag), tag_ids)
+ store.set(ent_type, {URI('http://example.com/me/entity#1234')}, self.schema.predicate(ns.bse.filesize), {1234})
+ store.set(ent_type, {URI('http://example.com/me/entity#4321')}, self.schema.predicate(ns.bse.filesize), {4321})
+ store.set(ent_type, {URI('http://example.com/me/entity#1234')}, self.schema.predicate(ns.bse.comment), {'hello world'})
+ # node_type must be a node from the schema
+ self.assertRaises(errors.ConsistencyError, list, store.fetch(self.schema.literal(ns.bsfs.Literal),
+ ast.filter.FilterExpression(), ast.fetch.FetchExpression()))
+ self.assertRaises(errors.ConsistencyError, list, store.fetch(self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.filter.FilterExpression(), ast.fetch.FetchExpression()))
+ # requires a filter and a fetch query
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), None, ast.fetch.FetchExpression()))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), 1234, ast.fetch.FetchExpression()))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), 'hello', ast.fetch.FetchExpression()))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression(), None))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression(), 1234))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression(), 'hello'))
+ # fetch emits triples
+ self.assertSetEqual(set(store.fetch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.fetch.Value(ns.bse.filesize, 'filesize'),
+ )), {
+ (URI('http://example.com/me/entity#1234'), 'filesize', 1234),
+ })
+ # fetch respects filter query
+ self.assertSetEqual(set(store.fetch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.IsIn('http://example.com/me/entity#1234', 'http://example.com/me/entity#4321'),
+ ast.fetch.Value(ns.bse.filesize, 'filesize'),
+ )), {
+ (URI('http://example.com/me/entity#1234'), 'filesize', 1234),
+ (URI('http://example.com/me/entity#4321'), 'filesize', 4321),
+ })
+ # fetch ignores missing data
+ self.assertSetEqual(set(store.fetch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.IsIn('http://example.com/me/entity#1234', 'http://example.com/me/entity#4321'),
+ ast.fetch.Value(ns.bse.comment, 'comment'),
+ )), {
+ (URI('http://example.com/me/entity#1234'), 'comment', 'hello world'),
+ })
+ # fetch emits all triples
+ self.assertSetEqual(set(store.fetch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.fetch.All(
+ ast.fetch.Value(ns.bse.filesize, 'filesize'),
+ ast.fetch.Node(ns.bse.tag, 'tag'),
+ )
+ )), {
+ (URI('http://example.com/me/entity#1234'), 'filesize', 1234),
+ (URI('http://example.com/me/entity#1234'), 'tag', URI('http://example.com/me/tag#1234')),
+ (URI('http://example.com/me/entity#1234'), 'tag', URI('http://example.com/me/tag#4321')),
+ })
+ # triples do not repeat
+ triples = list(store.fetch(self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.fetch.All(
+ ast.fetch.Value(ns.bse.filesize, 'filesize'),
+ ast.fetch.Node(ns.bse.tag, 'tag'),
+ )
+ ))
+ self.assertEqual(len(triples), 3)
def test_exists(self):
# store setup
@@ -501,20 +681,24 @@ class TestSparqlStore(unittest.TestCase):
store.schema = self.schema
# node type must be valid
- self.assertRaises(errors.ConsistencyError, store.create, self.schema.node(ns.bsfs.Entity).get_child(ns.bsfs.invalid), {
+ self.assertRaises(errors.ConsistencyError, store.create, self.schema.node(ns.bsfs.Entity).child(ns.bsfs.invalid), {
URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ # guid must be valid
+ self.assertRaises(ValueError, store.create, self.schema.node(ns.bsfs.Entity), {'http://example.com/me/foo and bar'})
+
# 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), {
+ self.assertSetEqual(set(store._graph), self.schema_triples | {
+ # 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)),
})
# 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), {
+ self.assertSetEqual(set(store._graph), self.schema_triples | {
# 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)),
@@ -525,7 +709,7 @@ class TestSparqlStore(unittest.TestCase):
# 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), {
+ self.assertSetEqual(set(store._graph), self.schema_triples | {
# 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)),
@@ -538,7 +722,7 @@ class TestSparqlStore(unittest.TestCase):
# 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), {
+ self.assertSetEqual(set(store._graph), self.schema_triples | {
# 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)),
@@ -562,7 +746,7 @@ class TestSparqlStore(unittest.TestCase):
p_comment = store.schema.predicate(ns.bse.comment)
p_author = store.schema.predicate(ns.bse.author)
p_tag = store.schema.predicate(ns.bse.tag)
- p_invalid = store.schema.predicate(ns.bsfs.Predicate).get_child(ns.bsfs.foo, range=store.schema.node(ns.bsfs.Tag))
+ p_invalid = store.schema.predicate(ns.bsfs.Predicate).child(ns.bsfs.foo, range=store.schema.node(ns.bsfs.Tag))
# create node instances
ent_ids = {
URI('http://example.com/me/entity#1234'),
@@ -585,12 +769,15 @@ class TestSparqlStore(unittest.TestCase):
store.create(user_type, user_ids)
# invalid node_type is not permitted
- self.assertRaises(errors.ConsistencyError, store.set, self.schema.node(ns.bsfs.Node).get_child(ns.bse.foo),
+ self.assertRaises(errors.ConsistencyError, store.set, self.schema.node(ns.bsfs.Node).child(ns.bse.foo),
ent_ids, p_comment, {'hello world'})
# invalid predicate is not permitted
self.assertRaises(errors.ConsistencyError, store.set, ent_type, ent_ids, p_invalid, {'http://example.com/me/tag#1234'})
+ # invalid guid is not permitted
+ self.assertRaises(ValueError, store.set, ent_type, {'http://example.com/me/foo and bar'}, p_filesize, {1234})
+
# predicate must match node_type
self.assertRaises(errors.ConsistencyError, store.set, tag_type, tag_ids, p_filesize, {1234})
@@ -760,6 +947,23 @@ class TestSparqlStore(unittest.TestCase):
# inexistent guids
self.assertRaises(errors.InstanceError, store.set, ent_type, {URI('http://example.com/me/entity#foobar')}, p_comment, {'xyz'})
+ # BinaryBlob values are base64 encoded
+ p_asset = store.schema.predicate(ns.bse.asset)
+ store.set(ent_type, ent_ids, p_asset, {bytes(range(128)), bytes(range(128, 256))})
+ blob1 = rdflib.Literal('AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=',
+ datatype=rdflib.URIRef(ns.bsl.BinaryBlob))
+ blob2 = rdflib.Literal('gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zNzs/Q0dLT1NXW19jZ2tvc3d7f4OHi4+Tl5ufo6err7O3u7/Dx8vP09fb3+Pn6+/z9/v8=',
+ datatype=rdflib.URIRef(ns.bsl.BinaryBlob))
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_asset.uri), blob1),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_asset.uri), blob2),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_asset.uri), blob1),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_asset.uri), blob2),
+ }))
+ # lit.value returns the original bytes value
+ self.assertSetEqual({lit.value for lit in store._graph.objects(None, rdflib.URIRef(p_asset.uri))},
+ {bytes(range(128)), bytes(range(128, 256))})
+
## main ##
diff --git a/test/triple_store/sparql/test_utils.py b/test/triple_store/sparql/test_utils.py
new file mode 100644
index 0000000..44a1299
--- /dev/null
+++ b/test/triple_store/sparql/test_utils.py
@@ -0,0 +1,152 @@
+
+# standard imports
+import operator
+import re
+import unittest
+
+# external imports
+import rdflib
+
+# bsie imports
+from bsfs.namespace import ns
+
+# objects to test
+from bsfs.triple_store.sparql.utils import GenHopName, Query
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+
+class TestGenHopName(unittest.TestCase):
+ def test_next(self):
+ # baseline
+ self.assertEqual(next(GenHopName(prefix='?foo', start=123)), '?foo123')
+ # respects prefix
+ self.assertEqual(next(GenHopName(prefix='?bar', start=123)), '?bar123')
+ # respects start
+ self.assertEqual(next(GenHopName(prefix='?foo', start=321)), '?foo321')
+ # counts up
+ cnt = GenHopName(prefix='?foo', start=998)
+ self.assertEqual(next(cnt), '?foo998')
+ self.assertEqual(next(cnt), '?foo999')
+ self.assertEqual(next(cnt), '?foo1000')
+ self.assertEqual(next(cnt), '?foo1001')
+
+ def test_essentials(self):
+ # can get the prefix
+ self.assertEqual(GenHopName(prefix='?foo', start=123).prefix, '?foo')
+ # can get the counter
+ self.assertEqual(GenHopName(prefix='?foo', start=123).curr, 122)
+
+
+class TestQuery(unittest.TestCase):
+ def setUp(self):
+ self.root_type = 'https://schema.bsfs.io/core/Entity'
+ self.root_head = '?root'
+ self.select = (('?head', 'name'), )
+ self.where = f'?root <{ns.bse.tag}> ?head'
+
+ def test_essentials(self):
+ # can access members
+ q = Query(self.root_type, self.root_head, self.select, self.where)
+ self.assertEqual(q.root_type, self.root_type)
+ self.assertEqual(q.root_head, self.root_head)
+ self.assertEqual(q.select, self.select)
+ self.assertEqual(q.where, self.where)
+ # comparison
+ self.assertEqual(q, Query(self.root_type, self.root_head, self.select, self.where))
+ self.assertEqual(hash(q), hash(Query(self.root_type, self.root_head, self.select, self.where)))
+ # comparison respects root_type
+ self.assertNotEqual(q, Query('https://schema.bsfs.io/core/Tag', self.root_head, self.select, self.where))
+ self.assertNotEqual(hash(q), hash(Query('https://schema.bsfs.io/core/Tag', self.root_head, self.select, self.where)))
+ # comparison respects root_head
+ self.assertNotEqual(q, Query(self.root_type, '?foo', self.select, self.where))
+ self.assertNotEqual(hash(q), hash(Query(self.root_type, '?foo', self.select, self.where)))
+ # comparison respects select
+ self.assertNotEqual(q, Query(self.root_type, self.root_head, (('?head', 'foo'), ), self.where))
+ self.assertNotEqual(hash(q), hash(Query(self.root_type, self.root_head, (('?head', 'foo'), ), self.where)))
+ # comparison respects where
+ self.assertNotEqual(q, Query(self.root_type, self.root_head, self.select, '?root bse:filename ?head'))
+ self.assertNotEqual(hash(q), hash(Query(self.root_type, self.root_head, self.select, '?root bse:filename ?head')))
+ # string conversion
+ self.assertEqual(str(q), q.query)
+ self.assertEqual(repr(q), "Query(https://schema.bsfs.io/core/Entity, ?root, (('?head', 'name'),), ?root <https://schema.bsfs.io/core/Entity#tag> ?head)")
+
+ def test_add(self):
+ q = Query(self.root_type, self.root_head, self.select, self.where)
+ # can only add a query
+ self.assertRaises(TypeError, operator.add, q, 1234)
+ self.assertRaises(TypeError, operator.add, q, 'foobar')
+ # root type and head must match
+ self.assertRaises(ValueError, operator.add, q, Query('https://schema.bsfs.io/core/Node/Tag', self.root_head))
+ self.assertRaises(ValueError, operator.add, q, Query(self.root_type, '?foobar'))
+ # select and were are combined
+ combo = q + Query(self.root_type, self.root_head, (('?foo', 'bar'), ), f'?root <{ns.bse.filename}> ?foo')
+ self.assertEqual(combo.select, (('?head', 'name'), ('?foo', 'bar')))
+ self.assertEqual(combo.where, f'?root <{ns.bse.tag}> ?head . ?root <{ns.bse.filename}> ?foo')
+ # select can be empty
+ combo = q + Query(self.root_type, self.root_head, None, f'?root <{ns.bse.filename}> ?foo')
+ self.assertEqual(combo.select, (('?head', 'name'), ))
+ combo = Query(self.root_type, self.root_head, None, f'?root <{ns.bse.filename}> ?foo') + q
+ self.assertEqual(combo.select, (('?head', 'name'), ))
+ combo = Query(self.root_type, self.root_head, None, self.where) + Query(self.root_type, self.root_head, None, f'?root <{ns.bse.filename}> ?foo')
+ self.assertEqual(combo.select, tuple())
+ # where can be empty
+ combo = q + Query(self.root_type, self.root_head, (('?foo', 'bar'), ))
+ self.assertEqual(combo.where, self.where)
+ combo = Query(self.root_type, self.root_head, (('?foo', 'bar'), )) + q
+ self.assertEqual(combo.where, self.where)
+ combo = Query(self.root_type, self.root_head, self.select) + Query(self.root_type, self.root_head, (('?foo', 'bar'), ))
+ self.assertEqual(combo.where, '')
+
+ def test_names(self):
+ self.assertEqual(Query(self.root_type, self.root_head, (('?head', 'name'), ), self.where).names,
+ ('name', ))
+ self.assertEqual(Query(self.root_type, self.root_head, (('?head', 'name'), ('?foo', 'bar')), self.where).names,
+ ('name', 'bar'))
+
+ def test_query(self):
+ def normalize(value):
+ value = value.strip()
+ value = value.lower()
+ value = value.replace(r'\n', ' ')
+ value, _ = re.subn('\s\s+', ' ', value)
+ return value
+ # query composes a valid query
+ q = Query(self.root_type, self.root_head, self.select, self.where)
+ self.assertEqual(normalize(q.query), normalize(f'select distinct ?root (?head as ?name) where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <https://schema.bsfs.io/core/Entity> . ?root <{ns.bse.tag}> ?head }} order by str(?root)'))
+ # select and where are optional
+ q = Query(self.root_type, self.root_head)
+ self.assertEqual(normalize(q.query), normalize(f'select distinct ?root where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <https://schema.bsfs.io/core/Entity> . }} order by str(?root)'))
+ # select and where need not to correspond
+ q = Query(self.root_type, self.root_head, (('?head', 'name'), ))
+ self.assertEqual(normalize(q.query), normalize(f'select distinct ?root (?head as ?name) where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <https://schema.bsfs.io/core/Entity> . }} order by str(?root)'))
+ # query is used for string representation
+ self.assertEqual(str(q), q.query)
+
+ def test_call(self):
+ graph = rdflib.Graph()
+ # schema
+ graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Document'), rdflib.URIRef(ns.rdfs.subClassOf), rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ # nodes
+ graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.rdf.type), rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ graph.add((rdflib.URIRef('http://example.com/doc#1234'), rdflib.URIRef(ns.rdf.type), rdflib.URIRef('https://schema.bsfs.io/core/Document')))
+ # links
+ graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.tag), rdflib.Literal('tag#1234', datatype=rdflib.XSD.string)))
+ graph.add((rdflib.URIRef('http://example.com/doc#1234'), rdflib.URIRef(ns.bse.tag), rdflib.Literal('tag#1234', datatype=rdflib.XSD.string)))
+ # run query on a given graph
+ query = Query(self.root_type, self.root_head, self.select, self.where)
+ self.assertSetEqual(set(query(graph)), {
+ (rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('tag#1234', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/doc#1234'), rdflib.Literal('tag#1234', datatype=rdflib.XSD.string)),
+ })
+ # query actually considers the passed graph
+ self.assertSetEqual(set(query(rdflib.Graph())), set())
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/triple_store/test_base.py b/test/triple_store/test_base.py
index a4b0559..4c4a9b6 100644
--- a/test/triple_store/test_base.py
+++ b/test/triple_store/test_base.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
@@ -35,6 +30,12 @@ class DummyBase(TripleStoreBase):
def schema(self, schema):
pass
+ def get(self, node_type, query):
+ pass
+
+ def fetch(self, node_type, filter, fetch):
+ pass
+
def exists(self, node_type, guids):
pass
diff --git a/test/utils/test_commons.py b/test/utils/test_commons.py
index ce73788..29e3046 100644
--- a/test/utils/test_commons.py
+++ b/test/utils/test_commons.py
@@ -1,14 +1,9 @@
-"""
-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
+from bsfs.utils.commons import typename, normalize_args
## code ##
@@ -21,6 +16,21 @@ class TestCommons(unittest.TestCase):
self.assertEqual(typename(123), 'int')
self.assertEqual(typename(None), 'NoneType')
+ def test_normalize_args(self):
+ # one argument
+ self.assertEqual(normalize_args(1), (1, ))
+ # pass as arguments
+ self.assertEqual(normalize_args(1,2,3), (1,2,3))
+ # pass as iterator
+ self.assertEqual(normalize_args(iter([1,2,3])), (1,2,3))
+ # pass as generator
+ self.assertEqual(normalize_args((i for i in range(1, 4))), (1,2,3))
+ self.assertEqual(normalize_args(i for i in range(1, 4)), (1,2,3)) # w/o brackets
+ # pass as iterable
+ self.assertEqual(normalize_args([1,2,3]), (1,2,3))
+ # pass an iterable with a single item
+ self.assertEqual(normalize_args([1]), (1, ))
+
## main ##
diff --git a/test/utils/test_uri.py b/test/utils/test_uri.py
index 770e65a..1c4c9f9 100644
--- a/test/utils/test_uri.py
+++ b/test/utils/test_uri.py
@@ -1,9 +1,4 @@
-"""
-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
@@ -40,6 +35,16 @@ class TestURI(unittest.TestCase):
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'))
+ # some characters are prohibited
+ self.assertFalse(URI.is_parseable('http://example.com/foo<bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo>bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo{bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo}bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo|bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo^bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo\\bar'))
+
# uri cannot end with a scheme delimiter
self.assertFalse(URI.is_parseable('http://'))
# port must be a number
@@ -164,10 +169,10 @@ class TestURI(unittest.TestCase):
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)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment') + 'hello', URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment') * 2, URI)
+ self.assertIsInstance(2 * URI('http://user@www.example.com:1234/path0/path1?query#fragment'), URI) # rmul
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#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)
@@ -176,7 +181,6 @@ class TestURI(unittest.TestCase):
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)
diff --git a/test/utils/test_uuid.py b/test/utils/test_uuid.py
index 49176d4..8f519d9 100644
--- a/test/utils/test_uuid.py
+++ b/test/utils/test_uuid.py
@@ -1,9 +1,4 @@
-"""
-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
@@ -83,6 +78,20 @@ class TestUCID(unittest.TestCase):
def test_from_path(self):
self.assertEqual(UCID.from_path(self._path), self._checksum)
+ def test_from_buffer(self):
+ with open(self._path, 'rb') as ifile:
+ self.assertEqual(UCID.from_buffer(ifile), self._checksum)
+ with open(self._path) as ifile:
+ self.assertEqual(UCID.from_buffer(ifile), self._checksum)
+
+ def test_from_bytes(self):
+ with open(self._path, 'rb') as ifile:
+ self.assertEqual(UCID.from_bytes(ifile.read()), self._checksum)
+
+ def test_from_dict(self):
+ self.assertEqual(UCID.from_dict({'hello': 'world', 'foo': 1234, 'bar': False}),
+ '8d2544395a0d2827e3d9ce8cd619d5e3f801e8126bf3f93ee5abd38158959585')
+
## main ##