aboutsummaryrefslogtreecommitdiffstats
path: root/bsfs/triple_store/base.py
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2022-12-18 14:21:11 +0100
committerMatthias Baumgartner <dev@igsor.net>2022-12-18 14:21:11 +0100
commit91437ba89d35bf482f3d9671bb99ef2fc69f5985 (patch)
treee9bfe27e5a641c040cfa8fe747a7cbb28091079c /bsfs/triple_store/base.py
parent87e4cd5a4581094f490f79d4f1cf91f51897660f (diff)
parente94368c75468e3e94382b12705e55d396249eaca (diff)
downloadbsfs-91437ba89d35bf482f3d9671bb99ef2fc69f5985.tar.gz
bsfs-91437ba89d35bf482f3d9671bb99ef2fc69f5985.tar.bz2
bsfs-91437ba89d35bf482f3d9671bb99ef2fc69f5985.zip
Merge branch 'develop' into main
Diffstat (limited to 'bsfs/triple_store/base.py')
-rw-r--r--bsfs/triple_store/base.py148
1 files changed, 148 insertions, 0 deletions
diff --git a/bsfs/triple_store/base.py b/bsfs/triple_store/base.py
new file mode 100644
index 0000000..6561262
--- /dev/null
+++ b/bsfs/triple_store/base.py
@@ -0,0 +1,148 @@
+"""
+
+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):
+ """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.
+ 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, **kwargs: typing.Any) -> 'TripleStoreBase': # pylint: disable=invalid-name # capitalized classmethod
+ """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
+ @abc.abstractmethod
+ 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],
+ ) -> typing.Iterable[URI]:
+ """Return those *guids* that exist and have type *node_type* or a subclass thereof."""
+
+ @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],
+ ):
+ """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 ##