diff options
-rw-r--r-- | bsfs/graph/graph.py | 24 | ||||
-rw-r--r-- | bsfs/graph/schema.nt | 18 | ||||
-rw-r--r-- | test/graph/test_graph.py | 98 |
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 ## |