aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2022-12-18 14:07:56 +0100
committerMatthias Baumgartner <dev@igsor.net>2022-12-18 14:07:56 +0100
commit3165c3609a5061135ff7393747f8dc3f7f7abe0c (patch)
tree5683aaab759d1930ce59237acde23e673562e87d
parentedd5390b6db1550f6a80a46f0eaf5f3916997532 (diff)
downloadbsfs-3165c3609a5061135ff7393747f8dc3f7f7abe0c.tar.gz
bsfs-3165c3609a5061135ff7393747f8dc3f7f7abe0c.tar.bz2
bsfs-3165c3609a5061135ff7393747f8dc3f7f7abe0c.zip
graph schema migration
-rw-r--r--bsfs/graph/graph.py24
-rw-r--r--bsfs/graph/schema.nt18
-rw-r--r--test/graph/test_graph.py98
3 files changed, 140 insertions, 0 deletions
diff --git a/bsfs/graph/graph.py b/bsfs/graph/graph.py
index 71973c2..4a36ff6 100644
--- a/bsfs/graph/graph.py
+++ b/bsfs/graph/graph.py
@@ -5,6 +5,7 @@ A copy of the license is provided with the project.
Author: Matthias Baumgartner, 2022
"""
# imports
+import os
import typing
# bsfs imports
@@ -35,6 +36,8 @@ class Graph():
def __init__(self, backend: TripleStoreBase, user: URI):
self._backend = backend
self._user = user
+ # ensure Graph schema requirements
+ self.migrate(self._backend.schema)
def __hash__(self) -> int:
return hash((type(self), self._backend, self._user))
@@ -55,7 +58,28 @@ class Graph():
"""Return the store's local schema."""
return self._backend.schema
+ def migrate(self, schema: Schema, append: bool = True) -> 'Graph':
+ """Migrate the current schema to a new *schema*.
+
+ Appends to the current schema by default; control this via *append*.
+ The `Graph` may add additional classes to the schema that are required for its interals.
+
"""
+ # check args
+ if not isinstance(schema, Schema):
+ raise TypeError(schema)
+ # append to current schema
+ if append:
+ schema = schema + self._backend.schema
+ # add Graph schema requirements
+ with open(os.path.join(os.path.dirname(__file__), 'schema.nt'), mode='rt', encoding='UTF-8') as ifile:
+ schema = schema + Schema.from_string(ifile.read())
+ # migrate schema in backend
+ # FIXME: consult access controls!
+ self._backend.schema = schema
+ # return self
+ return self
+
def nodes(self, node_type: URI, guids: typing.Iterable[URI]) -> _nodes.Nodes:
"""
node_type = self.schema.node(node_type)
diff --git a/bsfs/graph/schema.nt b/bsfs/graph/schema.nt
new file mode 100644
index 0000000..8612681
--- /dev/null
+++ b/bsfs/graph/schema.nt
@@ -0,0 +1,18 @@
+
+# generic prefixes
+prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+# bsfs prefixes
+prefix bsfs: <http://bsfs.ai/schema/>
+prefix bsm: <http://bsfs.ai/schema/Meta#>
+
+# literals
+xsd:integer rdfs:subClassOf bsfs:Literal .
+
+# predicates
+bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
diff --git a/test/graph/test_graph.py b/test/graph/test_graph.py
index eaeee0c..0b16527 100644
--- a/test/graph/test_graph.py
+++ b/test/graph/test_graph.py
@@ -84,6 +84,104 @@ class TestGraph(unittest.TestCase):
# node_type must be in the schema
self.assertRaises(KeyError, graph.nodes, ns.bsfs.Invalid, guids)
+ def test_migrate(self):
+ # setup
+ graph = Graph(self.backend, self.user)
+
+ # argument must be a schema
+ class Foo(): pass
+ self.assertRaises(TypeError, graph.migrate, 'hello world')
+ self.assertRaises(TypeError, graph.migrate, 1234)
+ self.assertRaises(TypeError, graph.migrate, Foo())
+
+ # cannot append inconsistent schema
+ self.assertRaises(errors.ConsistencyError, graph.migrate, schema.Schema({}, {
+ schema.Node(ns.bsfs.Entity,
+ schema.Node(ns.bsfs.Intermediate,
+ schema.Node(ns.bsfs.Node, None)))}), append=True)
+
+ # cannot migrate to inconsistent schema
+ self.assertRaises(errors.ConsistencyError, graph.migrate, schema.Schema({}, {
+ schema.Node(ns.bsfs.Entity,
+ schema.Node(ns.bsfs.Intermediate,
+ schema.Node(ns.bsfs.Node, None)))}), append=False)
+
+ # can migrate to compatible schema
+ target_1 = schema.Schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ graph.migrate(target_1)
+ # new schema is applied
+ self.assertLess(target_1, graph.schema)
+ # graph appends its predicates
+ self.assertEqual(graph.schema, target_1 + schema.Schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsm: <http://bsfs.ai/schema/Meta#>
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+ '''))
+
+ # can overwrite the current schema
+ target_2 = schema.Schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bse: <http://bsfs.ai/schema/Entity#>
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ ''')
+ graph.migrate(target_2, append=False)
+ # append overwrites existing predicates
+ self.assertFalse(target_1 <= graph.schema)
+ # new schema is applied
+ self.assertLess(target_2, graph.schema)
+ # graph appends its predicates
+ self.assertEqual(graph.schema, target_2 + schema.Schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsm: <http://bsfs.ai/schema/Meta#>
+ xsd:integer rdfs:subClassOf bsfs:Literal .
+ bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+ '''))
+
## main ##