aboutsummaryrefslogtreecommitdiffstats
path: root/bsfs/graph/nodes.py
diff options
context:
space:
mode:
Diffstat (limited to 'bsfs/graph/nodes.py')
-rw-r--r--bsfs/graph/nodes.py113
1 files changed, 57 insertions, 56 deletions
diff --git a/bsfs/graph/nodes.py b/bsfs/graph/nodes.py
index bc71a32..47b0217 100644
--- a/bsfs/graph/nodes.py
+++ b/bsfs/graph/nodes.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
from collections import abc
import time
@@ -30,15 +25,17 @@ __all__: typing.Sequence[str] = (
## code ##
class Nodes():
- """
+ """Container for graph nodes, provides operations on nodes.
+
+ NOTE: Should not be created directly but only via `bsfs.graph.Graph`.
NOTE: guids may or may not exist. This is not verified as nodes are created on demand.
"""
# triple store backend.
_backend: TripleStoreBase
- # user uri.
- _user: URI
+ # access controls.
+ _ac: ac.AccessControlBase
# node type.
_node_type: bsc.Node
@@ -49,31 +46,29 @@ class Nodes():
def __init__(
self,
backend: TripleStoreBase,
- user: URI,
+ access_control: ac.AccessControlBase,
node_type: bsc.Node,
guids: typing.Iterable[URI],
):
# set main members
self._backend = backend
- self._user = user
+ self._ac = access_control
self._node_type = node_type
- self._guids = set(guids)
- # create helper instances
- # FIXME: Assumes that the schema does not change while the instance is in use!
- self._ac = ac.NullAC(self._backend, self._user)
+ # convert to URI since this is not guaranteed by Graph
+ self._guids = {URI(guid) for guid in guids}
def __eq__(self, other: typing.Any) -> bool:
return isinstance(other, Nodes) \
and self._backend == other._backend \
- and self._user == other._user \
+ and self._ac == other._ac \
and self._node_type == other._node_type \
and self._guids == other._guids
def __hash__(self) -> int:
- return hash((type(self), self._backend, self._user, self._node_type, tuple(sorted(self._guids))))
+ return hash((type(self), self._backend, self._ac, self._node_type, tuple(sorted(self._guids))))
def __repr__(self) -> str:
- return f'{typename(self)}({self._backend}, {self._user}, {self._node_type}, {self._guids})'
+ return f'{typename(self)}({self._backend}, {self._ac}, {self._node_type}, {self._guids})'
def __str__(self) -> str:
return f'{typename(self)}({self._node_type}, {self._guids})'
@@ -94,44 +89,44 @@ class Nodes():
return self._backend.schema
def __add__(self, other: typing.Any) -> 'Nodes':
- """Concatenate guids. Backend, user, and node type must match."""
+ """Concatenate guids. Backend, AC, and node type must match."""
if not isinstance(other, type(self)):
return NotImplemented
if self._backend != other._backend:
raise ValueError(other)
- if self._user != other._user:
+ if self._ac != other._ac:
raise ValueError(other)
if self.node_type != other.node_type:
raise ValueError(other)
- return Nodes(self._backend, self._user, self.node_type, self._guids | other._guids)
+ return Nodes(self._backend, self._ac, self.node_type, self._guids | other._guids)
def __or__(self, other: typing.Any) -> 'Nodes':
- """Concatenate guids. Backend, user, and node type must match."""
+ """Concatenate guids. Backend, AC, and node type must match."""
return self.__add__(other)
def __sub__(self, other: typing.Any) -> 'Nodes':
- """Subtract guids. Backend, user, and node type must match."""
+ """Subtract guids. Backend, AC, and node type must match."""
if not isinstance(other, type(self)):
return NotImplemented
if self._backend != other._backend:
raise ValueError(other)
- if self._user != other._user:
+ if self._ac != other._ac:
raise ValueError(other)
if self.node_type != other.node_type:
raise ValueError(other)
- return Nodes(self._backend, self._user, self.node_type, self._guids - other._guids)
+ return Nodes(self._backend, self._ac, self.node_type, self._guids - other._guids)
def __and__(self, other: typing.Any) -> 'Nodes':
- """Intersect guids. Backend, user, and node type must match."""
+ """Intersect guids. Backend, AC, and node type must match."""
if not isinstance(other, type(self)):
return NotImplemented
if self._backend != other._backend:
raise ValueError(other)
- if self._user != other._user:
+ if self._ac != other._ac:
raise ValueError(other)
if self.node_type != other.node_type:
raise ValueError(other)
- return Nodes(self._backend, self._user, self.node_type, self._guids & other._guids)
+ return Nodes(self._backend, self._ac, self.node_type, self._guids & other._guids)
def __len__(self) -> int:
"""Return the number of guids."""
@@ -140,7 +135,7 @@ class Nodes():
def __iter__(self) -> typing.Iterator['Nodes']:
"""Iterate over individual guids. Returns `Nodes` instances."""
return iter(
- Nodes(self._backend, self._user, self.node_type, {guid})
+ Nodes(self._backend, self._ac, self.node_type, {guid})
for guid in self._guids
)
@@ -175,7 +170,7 @@ class Nodes():
self._backend.commit()
except (
- errors.PermissionDeniedError, # tried to set a protected predicate (ns.bsm.t_created)
+ errors.PermissionDeniedError, # tried to set a protected predicate
errors.ConsistencyError, # node types are not in the schema or don't match the predicate
errors.InstanceError, # guids/values don't have the correct type
TypeError, # value is supposed to be a Nodes instance
@@ -250,32 +245,38 @@ class Nodes():
# add access controls to fetch
fetch = self._ac.fetch_read(self.node_type, fetch)
- # compose filter ast
- filter = ast.filter.IsIn(self.guids) # pylint: disable=redefined-builtin
- # add access controls to filter
- filter = self._ac.filter_read(self.node_type, filter)
-
- # validate queries
- validate.Filter(self._backend.schema)(self.node_type, filter)
- validate.Fetch(self._backend.schema)(self.node_type, fetch)
-
- # process results, convert if need be
- def triple_iter():
- # query the backend
- triples = self._backend.fetch(self.node_type, filter, fetch)
- # process triples
- for root, name, raw in triples:
- # get node
- node = Nodes(self._backend, self._user, self.node_type, {root})
- # get path
- path, tail = name2path[name]
- # covert raw to value
- if isinstance(tail.range, bsc.Node):
- value = Nodes(self._backend, self._user, tail.range, {raw})
- else:
- value = raw
- # emit triple
- yield node, path, value
+ if len(self._guids) == 0:
+ # shortcut: no need to query; no triples
+ # FIXME: if the Fetch query can given by the user, we might want to check its validity
+ def triple_iter():
+ return []
+ else:
+ # compose filter ast
+ filter = ast.filter.IsIn(self.guids) # pylint: disable=redefined-builtin
+ # add access controls to filter
+ filter = self._ac.filter_read(self.node_type, filter) # type: ignore [assignment]
+
+ # validate queries
+ validate.Filter(self._backend.schema).validate(self.node_type, filter)
+ validate.Fetch(self._backend.schema).validate(self.node_type, fetch)
+
+ # process results, convert if need be
+ def triple_iter():
+ # query the backend
+ triples = self._backend.fetch(self.node_type, filter, fetch)
+ # process triples
+ for root, name, raw in triples:
+ # get node
+ node = Nodes(self._backend, self._ac, self.node_type, {root})
+ # get path
+ path, tail = name2path[name]
+ # covert raw to value
+ if isinstance(tail.range, bsc.Node):
+ value = Nodes(self._backend, self._ac, tail.range, {raw})
+ else:
+ value = raw
+ # emit triple
+ yield node, path, value
# simplify by default
view_kwargs['node'] = view_kwargs.get('node', len(self._guids) != 1)
@@ -393,7 +394,7 @@ class Nodes():
self._backend.create(node_type, missing)
# add bookkeeping triples
self._backend.set(node_type, missing,
- self._backend.schema.predicate(ns.bsm.t_created), [time.time()])
+ self._backend.schema.predicate(ns.bsn.t_created), [time.time()])
# add permission triples
self._ac.create(node_type, missing)
# return available nodes