aboutsummaryrefslogtreecommitdiffstats
path: root/bsfs/graph/graph.py
diff options
context:
space:
mode:
Diffstat (limited to 'bsfs/graph/graph.py')
-rw-r--r--bsfs/graph/graph.py100
1 files changed, 60 insertions, 40 deletions
diff --git a/bsfs/graph/graph.py b/bsfs/graph/graph.py
index df2e3a5..1b4c212 100644
--- a/bsfs/graph/graph.py
+++ b/bsfs/graph/graph.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import os
import typing
@@ -28,9 +23,7 @@ __all__: typing.Sequence[str] = (
## code ##
class Graph():
- """The Graph class is
-
- The Graph class provides a convenient interface to query and access a graph.
+ """The Graph class provides a convenient interface to query and access a graph.
Since it logically builds on the concept of graphs it is easier to
navigate than raw triple stores. Naturally, it uses a triple store
as *backend*. It also controls actions via access permissions to a *user*.
@@ -40,31 +33,33 @@ class Graph():
# link to the triple storage backend.
_backend: TripleStoreBase
- # user uri.
- _user: URI
+ # access controls.
+ _ac: ac.AccessControlBase
- def __init__(self, backend: TripleStoreBase, user: URI):
+ def __init__(
+ self,
+ backend: TripleStoreBase,
+ access_control: ac.AccessControlBase,
+ ):
+ # store members
self._backend = backend
- self._user = user
- self._resolver = resolve.Filter(self._backend.schema)
- self._validate = validate.Filter(self._backend.schema)
- self._ac = ac.NullAC(self._backend, self._user)
+ self._ac = access_control
# ensure Graph schema requirements
self.migrate(self._backend.schema)
def __hash__(self) -> int:
- return hash((type(self), self._backend, self._user))
+ return hash((type(self), self._backend, self._ac))
def __eq__(self, other) -> bool:
return isinstance(other, type(self)) \
and self._backend == other._backend \
- and self._user == other._user
+ and self._ac == other._ac
def __repr__(self) -> str:
- return f'{typename(self)}(backend={repr(self._backend)}, user={self._user})'
+ return f'{typename(self)}({repr(self._backend)}, {self._ac})'
def __str__(self) -> str:
- return f'{typename(self)}({str(self._backend)}, {self._user})'
+ return f'{typename(self)}({str(self._backend)})'
@property
def schema(self) -> bsc.Schema:
@@ -90,9 +85,6 @@ class Graph():
# migrate schema in backend
# FIXME: consult access controls!
self._backend.schema = schema
- # re-initialize members
- self._resolver.schema = self.schema
- self._validate.schema = self.schema
# return self
return self
@@ -104,41 +96,69 @@ class Graph():
*node_type*) once some data is assigned to them.
"""
+ # get node type
type_ = self.schema.node(node_type)
# NOTE: Nodes constructor materializes guids.
- return _nodes.Nodes(self._backend, self._user, type_, guids)
+ return _nodes.Nodes(self._backend, self._ac, type_, guids)
def node(self, node_type: URI, guid: URI) -> _nodes.Nodes:
"""Return node *guid* of type *node_type* as a `bsfs.graph.Nodes` instance.
- Note that the *guids* need not to exist (however, the *node_type* has
+ Note that the *guid* need not to exist (however, the *node_type* has
to be part of the schema). An inexistent guid will be created (using
*node_type*) once some data is assigned to them.
"""
return self.nodes(node_type, {guid})
- def get(self, node_type: URI, query: ast.filter.FilterExpression) -> _nodes.Nodes: # FIXME: How about empty query?
- """Return a `Nodes` instance over all nodes of type *node_type* that match the *subject* query."""
- # get node type
- type_ = self.schema.node(node_type)
- # resolve Nodes instances
- query = self._resolver(type_, query)
- # add access controls to query
- query = self._ac.filter_read(type_, query)
- # validate query
- self._validate(type_, query)
- # query the backend
- guids = self._backend.get(type_, query) # no need to materialize
+ def empty(self, node_type: URI) -> _nodes.Nodes:
+ """Return a `Nodes` instance with type *node_type* but no nodes."""
+ return self.nodes(node_type, set())
+
+ def get(
+ self,
+ node_type: URI,
+ query: typing.Optional[ast.filter.FilterExpression],
+ ) -> _nodes.Nodes:
+ """Return a `Nodes` instance over all nodes of type *node_type* that match the *query*."""
# return Nodes instance
- return _nodes.Nodes(self._backend, self._user, type_, guids)
+ type_ = self.schema.node(node_type)
+ return _nodes.Nodes(self._backend, self._ac, type_, self.__get(node_type, query))
+
+ def sorted(
+ self,
+ node_type: URI,
+ query: typing.Optional[ast.filter.FilterExpression],
+ # FIXME: sort ast
+ ) -> typing.Iterator[_nodes.Nodes]:
+ """Return a iterator over `Nodes` instances over all nodes of type *node_type* that match the *query*."""
+ # FIXME: Order should be a parameter
+ # return iterator over Nodes instances
+ type_ = self.schema.node(node_type)
+ for guid in self.__get(node_type, query):
+ yield _nodes.Nodes(self._backend, self._ac, type_, {guid})
def all(self, node_type: URI) -> _nodes.Nodes:
"""Return all instances of type *node_type*."""
+ type_ = self.schema.node(node_type)
+ return _nodes.Nodes(self._backend, self._ac, type_, self.__get(node_type, None))
+
+ def __get(
+ self,
+ node_type: URI,
+ query: typing.Optional[ast.filter.FilterExpression],
+ ) -> typing.Iterator[URI]:
+ """Build and execute a get query."""
# get node type
type_ = self.schema.node(node_type)
- guids = self._backend.get(type_, None) # no need to materialize
- return _nodes.Nodes(self._backend, self._user, type_, guids)
-
+ # resolve Nodes instances
+ query = resolve.Filter(self._backend.schema).resolve(type_, query)
+ # add access controls to query
+ query = self._ac.filter_read(type_, query)
+ # validate query
+ if query is not None:
+ validate.Filter(self._backend.schema).validate(type_, query)
+ # query the backend and return the (non-materialized) result
+ return self._backend.get(type_, query)
## EOF ##