From e8492489098ef5f8566214e083cd2c2d1d449f5a Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Thu, 8 Dec 2022 16:36:19 +0100 Subject: sparql triple store and graph (nodes, mostly) --- bsfs/triple_store/base.py | 128 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 bsfs/triple_store/base.py (limited to 'bsfs/triple_store/base.py') diff --git a/bsfs/triple_store/base.py b/bsfs/triple_store/base.py new file mode 100644 index 0000000..a2668c3 --- /dev/null +++ b/bsfs/triple_store/base.py @@ -0,0 +1,128 @@ +""" + +Part of the BlackStar filesystem (bsfs) module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import abc +import typing + +# inner-module imports +from bsfs.utils import URI, typename +import bsfs.schema as _schema + +# exports +__all__: typing.Sequence[str] = ( + 'TripleStoreBase', + ) + + +## code ## + +class TripleStoreBase(abc.ABC): + """ + """ + + # storage's URI. None implies a temporary location. + uri: typing.Optional[URI] = None + + def __init__(self, uri: typing.Optional[URI] = None): + self.uri = uri + + def __hash__(self) -> int: + uri = self.uri if self.uri is not None else id(self) + return hash((type(self), uri)) + + def __eq__(self, other) -> bool: + return isinstance(other, type(self)) \ + and (( self.uri is not None \ + and other.uri is not None \ + and self.uri == other.uri ) \ + or id(self) == id(other)) + + def __repr__(self) -> str: + return f'{typename(self)}(uri={self.uri})' + + def __str__(self) -> str: + return f'{typename(self)}(uri={self.uri})' + + def is_persistent(self) -> bool: + """Return True if data is stored persistently.""" + return self.uri is not None + + + @classmethod + @abc.abstractmethod + def Open( + cls, + uri: str, + **kwargs: typing.Any, + ) -> 'TripleStoreBase': + """Return a TripleStoreBase instance connected to *uri*.""" + + @abc.abstractmethod + def commit(self): + """Commit the current transaction.""" + + @abc.abstractmethod + def rollback(self): + """Undo changes since the last commit.""" + + @property + @abc.abstractmethod + def schema(self) -> _schema.Schema: + """Return the store's local schema.""" + + @schema.setter + def schema(self, schema: _schema.Schema): + """Migrate to new schema by adding or removing class definitions. + + Commits before and after the migration. + + Instances of removed classes will be deleted irreversably. + Note that modifying an existing class is not directly supported. + Also, it is generally discouraged, since changing definitions may + lead to inconsistencies across multiple clients in a distributed + setting. Instead, consider introducing a new class under its own + uri. Such a migration would look as follows: + + 1. Add new class definitions. + 2. Create instances of the new classes and copy relevant data. + 3. Remove the old definitions. + + To modify a class, i.e., re-use a previous uri with a new + class definition, you would have to migrate via temporary + class definitions, and thus repeat the above procedure two times. + + """ + + @abc.abstractmethod + def exists( + self, + node_type: _schema.Node, + guids: typing.Iterable[URI], + ): + """ + """ + + @abc.abstractmethod + def create( + self, + node_type: _schema.Node, + guids: typing.Iterable[URI], + ): + """Create *guid* nodes with type *subject*.""" + + @abc.abstractmethod + def set( + self, + node_type: _schema.Node, # FIXME: is the node_type even needed? Couldn't I infer from the predicate? + guids: typing.Iterable[URI], + predicate: _schema.Predicate, + values: typing.Iterable[typing.Any], + ): + """ + """ + +## EOF ## -- cgit v1.2.3 From 58496960926a56149c10d64e01b6df7d048eed0e Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sun, 18 Dec 2022 14:11:27 +0100 Subject: triple store Open interface --- bsfs/triple_store/base.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'bsfs/triple_store/base.py') diff --git a/bsfs/triple_store/base.py b/bsfs/triple_store/base.py index a2668c3..942a16b 100644 --- a/bsfs/triple_store/base.py +++ b/bsfs/triple_store/base.py @@ -54,11 +54,7 @@ class TripleStoreBase(abc.ABC): @classmethod @abc.abstractmethod - def Open( - cls, - uri: str, - **kwargs: typing.Any, - ) -> 'TripleStoreBase': + def Open(cls, **kwargs: typing.Any) -> 'TripleStoreBase': # pylint: disable=invalid-name # capitalized classmethod """Return a TripleStoreBase instance connected to *uri*.""" @abc.abstractmethod @@ -75,6 +71,7 @@ class TripleStoreBase(abc.ABC): """Return the store's local schema.""" @schema.setter + @abc.abstractmethod def schema(self, schema: _schema.Schema): """Migrate to new schema by adding or removing class definitions. -- cgit v1.2.3 From e19c8f9d0818a147832df0945188ea14de9c7690 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sun, 18 Dec 2022 14:15:18 +0100 Subject: documentation, types, and style fixes --- bsfs/triple_store/base.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) (limited to 'bsfs/triple_store/base.py') diff --git a/bsfs/triple_store/base.py b/bsfs/triple_store/base.py index 942a16b..6561262 100644 --- a/bsfs/triple_store/base.py +++ b/bsfs/triple_store/base.py @@ -21,7 +21,21 @@ __all__: typing.Sequence[str] = ( ## code ## class TripleStoreBase(abc.ABC): - """ + """TripleStore base class. + + Use the `Open` method to create a new instance and to initialize + the required structures. + + Triple stores express a graph via its (subject, predicate, object) triples. + They provides methods to add and remove triples, and to query the storage + for given graph structures. The subject is always a node in the graph, + whereas nodes are identifiable by a unique URI. Note that blank nodes + (without an explicit URI) are not supported. The object can be another + Node or a Literal value. The relation between a subject and an object + is expressed via a Predicate. The graph structures are governed by a + schema that defines which Node, Literal, and Predicate classes exist + and how they can interact (see `bsfs.schema.Schema`). + """ # storage's URI. None implies a temporary location. @@ -99,9 +113,8 @@ class TripleStoreBase(abc.ABC): self, node_type: _schema.Node, guids: typing.Iterable[URI], - ): - """ - """ + ) -> typing.Iterable[URI]: + """Return those *guids* that exist and have type *node_type* or a subclass thereof.""" @abc.abstractmethod def create( @@ -119,7 +132,17 @@ class TripleStoreBase(abc.ABC): predicate: _schema.Predicate, values: typing.Iterable[typing.Any], ): - """ + """Add triples to the graph. + + It is assumed that all of *guids* exist and have *node_type*. + This method adds a triple (guid, predicate, value) for every guid in + *guids* and each value in *values* (cartesian product). Note that + *values* must have length one for unique predicates, and that + currently existing values will be overwritten in this case. + It also verifies that all symbols are part of the schema and that + the *predicate* matches the *node_type*. + Raises `bsfs.errors.ConsistencyError` if these assumptions are violated. + """ ## EOF ## -- cgit v1.2.3