From 36d07cc6e0ec0f53001bfc5045437a374ebb895f Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Thu, 2 Mar 2023 14:55:57 +0100 Subject: empty fetch result in Nodes --- bsfs/graph/nodes.py | 58 ++++++++++++++++++++++++-------------------- test/graph/ac/test_null.py | 9 +++++++ test/graph/test_nodes.py | 60 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 97 insertions(+), 30 deletions(-) diff --git a/bsfs/graph/nodes.py b/bsfs/graph/nodes.py index 91cbb5d..c6bd5d8 100644 --- a/bsfs/graph/nodes.py +++ b/bsfs/graph/nodes.py @@ -249,32 +249,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._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 + 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)(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._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) diff --git a/test/graph/ac/test_null.py b/test/graph/ac/test_null.py index 544a01e..6053f81 100644 --- a/test/graph/ac/test_null.py +++ b/test/graph/ac/test_null.py @@ -136,6 +136,15 @@ class TestNullAC(unittest.TestCase): # query can be none self.assertIsNone(ac.filter_read(self.ent_type, None)) + def test_fetch_read(self): + query = ast.fetch.All( + ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bse.label, 'tag_label')), + ast.fetch.Node(ns.bse.tag, 'tag_node'), + ast.fetch.Value(ns.bse.iso, 'iso')) + ac = NullAC(self.backend, self.user) + # NullAC returns query + self.assertEqual(query, ac.fetch_read(self.ent_type, query)) + ## main ## diff --git a/test/graph/test_nodes.py b/test/graph/test_nodes.py index 9541656..bf73e6e 100644 --- a/test/graph/test_nodes.py +++ b/test/graph/test_nodes.py @@ -346,6 +346,10 @@ class TestNodes(unittest.TestCase): Nodes(self.backend, self.ac, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')})) self.assertSetEqual(curr, set(self.backend._graph)) + # can set on empty nodes + nodes = Nodes(self.backend, self.ac, self.ent_type, {}) + self.assertEqual(nodes, nodes.set(self.p_filesize.uri, 1234)) + def test_set_from_iterable(self): self.assertSetEqual(set(self.backend._graph), self.schema_triples | set()) @@ -388,6 +392,11 @@ class TestNodes(unittest.TestCase): (self.p_author.uri, Nodes(self.backend, self.ac, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')})))) self.assertSetEqual(curr, set(self.backend._graph)) + # can set on empty nodes + nodes = Nodes(self.backend, self.ac, self.ent_type, {}) + self.assertEqual(nodes, nodes.set_from_iterable([(self.p_filesize.uri, 1234)])) + + def test_get(self): # setup: add some instances Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}) \ @@ -403,6 +412,7 @@ class TestNodes(unittest.TestCase): .set(bst.label, 'tag_label_4321') # setup: get nodes instance nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids) + # must pass at least one path self.assertRaises(AttributeError, nodes.get) # view must be list or dict @@ -467,10 +477,52 @@ class TestNodes(unittest.TestCase): Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {'hello world'}, }) - # FIXME: What if I call `get` with a single predicate and a single node, but - # that node has no value for that predicate? - # so, essentially, what if triples is empty? -> Also check in test_result! - raise NotImplementedError() + # results can be empty + nodes = Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}) # has filesize, tag but no comment + # unique paths return the default value + self.assertIsNone(nodes.get(ns.bse.author)) + self.assertEqual(nodes.get(ns.bse.author, default=1234), 1234) + # non-unique paths return an empty set + self.assertSetEqual(nodes.get(ns.bse.comment), set()) + + # nodes can have no guids + nodes = Nodes(self.backend, self.ac, self.ent_type, set()) + # empty nodes does not excuse an invalid request + self.assertRaises(TypeError, nodes.get, 1234) + self.assertRaises(errors.ConsistencyError, nodes.get, ns.bse.invalid) + # list view always returns an empty list + self.assertListEqual(list(nodes.get(ns.bse.comment, view=list)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=True)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, path=True)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=True, path=True, value=True)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=False)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, path=False)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=False, path=False, value=False)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=True)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, path=True)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=True, path=True, value=True)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=False)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, path=False)), []) + self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=False, path=False, value=False)), []) + # dict view returns an empty dict or an empty set + self.assertDictEqual(nodes.get(ns.bse.comment, view=dict), {}) + self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, node=True), {}) + self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, path=True), {}) + self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, node=True, path=True, value=True, default=None), {}) + self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False), set()) + self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, path=False), {}) + self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False, path=False), set()) + self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False, path=False, value=False, default=None), set()) + self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict), {}) + self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=True), {}) + self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, path=True), {}) + self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=True, path=True, value=True, default=None), {}) + self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False), {}) + self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, path=False), {}) + self.assertSetEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False, path=False), set()) + self.assertSetEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False, path=False, value=False, default=None), set()) + def test_getattr(self): nodes = Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}) -- cgit v1.2.3