From dc0d3858e2e0e420a8972aa7c16ea792ac19fb6f Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Wed, 1 Nov 2023 10:34:48 +0100 Subject: [PATCH 1/6] Remove duplicated code in store --- rdflib_django/store.py | 75 +++++++++++------------------------------- 1 file changed, 20 insertions(+), 55 deletions(-) diff --git a/rdflib_django/store.py b/rdflib_django/store.py index 3d386d5..27088ac 100644 --- a/rdflib_django/store.py +++ b/rdflib_django/store.py @@ -94,6 +94,22 @@ def _get_named_graph(self, context): identifier=context.identifier, store=self.store )[0] + def _get_query_sets_for_triple(self, triple, context): + s, p, o = triple + named_graph = self._get_named_graph(context) + query_sets = _get_query_sets_for_object(o) + filter_parameters = dict() + if named_graph is not None: + filter_parameters["context_id"] = named_graph.id + if s is not None: + filter_parameters["subject"] = s + if p is not None: + filter_parameters["predicate"] = p + if o is not None: + filter_parameters["object"] = o + query_sets = [qs.filter(**filter_parameters) for qs in query_sets] + return query_sets + def open(self, configuration: str, create: bool = False) -> Optional[int]: """ Opens the underlying store. This is only necessary when opening @@ -167,22 +183,7 @@ def remove(self, triple, context=None): """ Removes a triple from the store. """ - s, p, o = triple - named_graph = self._get_named_graph(context) - query_sets = _get_query_sets_for_object(o) - - filter_parameters = dict() - if named_graph is not None: - filter_parameters["context_id"] = named_graph.id - if s is not None: - filter_parameters["subject"] = s - if p is not None: - filter_parameters["predicate"] = p - if o is not None: - filter_parameters["object"] = o - - query_sets = [qs.filter(**filter_parameters) for qs in query_sets] - + query_sets = self._get_query_sets_for_triple(triple, context) for qs in query_sets: qs.delete() @@ -190,22 +191,7 @@ def triples(self, triple, context=None): """ Returns all triples in the current store. """ - s, p, o = triple - named_graph = self._get_named_graph(context) - query_sets = _get_query_sets_for_object(o) - - filter_parameters = dict() - if named_graph is not None: - filter_parameters["context_id"] = named_graph.id - if s is not None: - filter_parameters["subject"] = s - if p is not None: - filter_parameters["predicate"] = p - if o is not None: - filter_parameters["object"] = o - - query_sets = [qs.filter(**filter_parameters) for qs in query_sets] - + query_sets = self._get_query_sets_for_triple(triple, context) for qs in query_sets: for statement in qs: triple = statement.as_triple() @@ -217,29 +203,8 @@ def __len__(self, context=None): """ Returns the number of statements in this Graph. """ - named_graph = self._get_named_graph(context) - if named_graph is not None: - return ( - models.LiteralStatement.objects.filter( - context_id=named_graph.id - ).count() - + models.URIStatement.objects.filter( - context_id=named_graph.id - ).count() - ) - else: - return ( - models.URIStatement.objects.values( - "subject", "predicate", "object" - ) - .distinct() - .count() - + models.LiteralStatement.objects.values( - "subject", "predicate", "object" - ) - .distinct() - .count() - ) + query_sets = self._get_query_sets_for_triple((None, None, None), context) + return sum(qs.values("subject", "predicate", "object").distinct().count() for qs in query_sets) #################### # CONTEXT MANAGEMENT From c87fdd5d71cff7b0fcb5088d4314fa20f0947c14 Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Wed, 1 Nov 2023 10:41:05 +0100 Subject: [PATCH 2/6] Test conjunctive graphs --- test/test_store.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/test_store.py b/test/test_store.py index 5b10849..7bf08a1 100644 --- a/test/test_store.py +++ b/test/test_store.py @@ -6,10 +6,12 @@ import rdflib from django import test -from rdflib.graph import Graph +from rdflib.graph import Graph, ConjunctiveGraph from rdflib.namespace import RDF, RDFS, Namespace from rdflib.term import BNode, Literal, URIRef +from rdflib_django.store import DjangoStore + EX = Namespace("http://www.example.com/") @@ -111,3 +113,30 @@ def test_falsy_literals(self): self.assertEqual( list(self.graph), [(artis, EX["prop"], Literal(False))] ) + + def testConjunctiveGraph(self): + ex = Namespace("https://www.example.org/") + store1 = DjangoStore(identifier="store1") + store2 = DjangoStore(identifier="store2") + + g1 = Graph(store1, identifier=ex.g1) + g2 = ConjunctiveGraph(store1, identifier=ex.g2) + g3 = Graph(store2, identifier=ex.g3) + + g1.add((ex.a, ex.a, ex.a)) + g2.add((ex.b, ex.b, ex.b)) + g3.add((ex.c, ex.c, ex.c)) + + self.assertEquals(len(g1), 1) + self.assertEquals(len(g2), 2) + self.assertEquals(len(g3), 1) + + self.assertEquals(set(g1), {(ex.a, ex.a, ex.a)}) + self.assertEquals(set(g2), {(ex.a, ex.a, ex.a), (ex.b, ex.b, ex.b)}) + self.assertEquals(set(g3), {(ex.c, ex.c, ex.c)}) + + g2.remove((None, None, None)) + + self.assertEquals(list(g1), []) + self.assertEquals(list(g2), []) + self.assertEquals(list(g3), [(ex.c, ex.c, ex.c)]) From 5ef430badd763839ff7e166f93051ec6c3a6becb Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Wed, 1 Nov 2023 11:20:53 +0100 Subject: [PATCH 3/6] Fix bug that leaks triples between stores --- rdflib_django/store.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rdflib_django/store.py b/rdflib_django/store.py index 27088ac..d9752dd 100644 --- a/rdflib_django/store.py +++ b/rdflib_django/store.py @@ -95,12 +95,22 @@ def _get_named_graph(self, context): )[0] def _get_query_sets_for_triple(self, triple, context): + """ + Determine correct query sets based on triple and context. + + Respects None as wildcard. If the object in triple is None the resulting list has two query sets, one for + Literal results and one for URIRef results. + + This method always returns a list of size at least one. + """ # noqa: E501 s, p, o = triple named_graph = self._get_named_graph(context) query_sets = _get_query_sets_for_object(o) filter_parameters = dict() if named_graph is not None: filter_parameters["context_id"] = named_graph.id + else: + filter_parameters["context__store"] = self.store if s is not None: filter_parameters["subject"] = s if p is not None: From 250615cb6033894c7967974fdddb4929bb909024 Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Wed, 1 Nov 2023 12:52:36 +0100 Subject: [PATCH 4/6] Fix functions in utils --- rdflib_django/utils.py | 15 +++++++------ test/test_store.py | 27 ------------------------ test/test_utils.py | 48 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 34 deletions(-) create mode 100644 test/test_utils.py diff --git a/rdflib_django/utils.py b/rdflib_django/utils.py index 0ae1de5..5b8d038 100644 --- a/rdflib_django/utils.py +++ b/rdflib_django/utils.py @@ -8,15 +8,16 @@ from .store import DEFAULT_STORE, DjangoStore -def get_conjunctive_graph(store_id=None): +def get_conjunctive_graph(store_id=DEFAULT_STORE, identifier=None): """ Returns an open conjunctive graph. - """ - if not store_id: - store_id = DEFAULT_STORE - store = DjangoStore(DEFAULT_STORE) - graph = ConjunctiveGraph(store=store, identifier=store_id) + The returned graph reads triples from all graphs in the store. + Write operations happen on the graph specified by the identifier parameter + or a graph identified by a blank node if the identifier is not provided. + """ + store = DjangoStore(identifier=store_id) + graph = ConjunctiveGraph(store=store, identifier=identifier) if graph.open(None) != VALID_STORE: raise ValueError( "The store identified by {} is not a valid store".format(store_id) @@ -31,7 +32,7 @@ def get_named_graph(identifier, store_id=DEFAULT_STORE, create=True): if not isinstance(identifier, URIRef): identifier = URIRef(identifier) - store = DjangoStore(store_id) + store = DjangoStore(identifier=store_id) graph = Graph(store, identifier=identifier) if graph.open(None, create=create) != VALID_STORE: raise ValueError( diff --git a/test/test_store.py b/test/test_store.py index 7bf08a1..02e13cb 100644 --- a/test/test_store.py +++ b/test/test_store.py @@ -113,30 +113,3 @@ def test_falsy_literals(self): self.assertEqual( list(self.graph), [(artis, EX["prop"], Literal(False))] ) - - def testConjunctiveGraph(self): - ex = Namespace("https://www.example.org/") - store1 = DjangoStore(identifier="store1") - store2 = DjangoStore(identifier="store2") - - g1 = Graph(store1, identifier=ex.g1) - g2 = ConjunctiveGraph(store1, identifier=ex.g2) - g3 = Graph(store2, identifier=ex.g3) - - g1.add((ex.a, ex.a, ex.a)) - g2.add((ex.b, ex.b, ex.b)) - g3.add((ex.c, ex.c, ex.c)) - - self.assertEquals(len(g1), 1) - self.assertEquals(len(g2), 2) - self.assertEquals(len(g3), 1) - - self.assertEquals(set(g1), {(ex.a, ex.a, ex.a)}) - self.assertEquals(set(g2), {(ex.a, ex.a, ex.a), (ex.b, ex.b, ex.b)}) - self.assertEquals(set(g3), {(ex.c, ex.c, ex.c)}) - - g2.remove((None, None, None)) - - self.assertEquals(list(g1), []) - self.assertEquals(list(g2), []) - self.assertEquals(list(g3), [(ex.c, ex.c, ex.c)]) diff --git a/test/test_utils.py b/test/test_utils.py new file mode 100644 index 0000000..eea0194 --- /dev/null +++ b/test/test_utils.py @@ -0,0 +1,48 @@ +""" +Unit tests for the utils module. +""" +from django import test +from rdflib import Namespace + +from rdflib_django.utils import get_named_graph, get_conjunctive_graph + +EX = Namespace("https://www.example.com/") + + +class GraphTest(test.TestCase): + """ + Checks on the utils module. + """ + + def test_conjunctive_and_named_graphs(self): + g1 = get_named_graph(EX.g1) + g2 = get_conjunctive_graph() + g1_s2 = get_named_graph(EX.g1, "s2") + g3 = get_conjunctive_graph("s2") + g4 = get_named_graph(EX.g4, store_id="s3") + g5 = get_conjunctive_graph("s3", identifier=EX.g4) + + g1.add((EX.a, EX.a, EX.a)) + g2.add((EX.b, EX.b, EX.b)) + g1_s2.add((EX.c, EX.c, EX.c)) + g3.add((EX.d, EX.d, EX.d)) + g4.add((EX.x, EX.x, EX.x)) + g5.add((EX.y, EX.y, EX.y)) + + self.assertEquals(len(g1), 1) + self.assertEquals(len(g1_s2), 1) + self.assertEquals(len(g2), 2) + self.assertEquals(len(g3), 2) + self.assertEquals(len(g4), 2) + + self.assertEquals(set(g1), {(EX.a, EX.a, EX.a)}) + self.assertEquals(set(g2), {(EX.a, EX.a, EX.a), (EX.b, EX.b, EX.b)}) + self.assertEquals(set(g1_s2), {(EX.c, EX.c, EX.c)}) + self.assertEquals(set(g3), {(EX.c, EX.c, EX.c), (EX.d, EX.d, EX.d)}) + self.assertEquals(set(g4), set(g5)) + + g2.remove((None, None, None)) + + self.assertEquals(list(g1), []) + self.assertEquals(list(g2), []) + self.assertEquals(list(g1_s2), [(EX.c, EX.c, EX.c)]) From 9913e70078a89aea38624e125355bb99c059cf6a Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Wed, 1 Nov 2023 13:35:53 +0100 Subject: [PATCH 5/6] Code style cleanup --- rdflib_django/store.py | 18 +++++++++++------- test/test_store.py | 4 +--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/rdflib_django/store.py b/rdflib_django/store.py index d9752dd..612b8d6 100644 --- a/rdflib_django/store.py +++ b/rdflib_django/store.py @@ -97,12 +97,13 @@ def _get_named_graph(self, context): def _get_query_sets_for_triple(self, triple, context): """ Determine correct query sets based on triple and context. - - Respects None as wildcard. If the object in triple is None the resulting list has two query sets, one for - Literal results and one for URIRef results. - + + Respects None as wildcard. + If the object in triple is None the resulting list has two query sets, + one for Literal results and one for URIRef results. + This method always returns a list of size at least one. - """ # noqa: E501 + """ s, p, o = triple named_graph = self._get_named_graph(context) query_sets = _get_query_sets_for_object(o) @@ -213,8 +214,11 @@ def __len__(self, context=None): """ Returns the number of statements in this Graph. """ - query_sets = self._get_query_sets_for_triple((None, None, None), context) - return sum(qs.values("subject", "predicate", "object").distinct().count() for qs in query_sets) + queries = self._get_query_sets_for_triple((None, None, None), context) + return sum( + qs.values("subject", "predicate", "object").distinct().count() + for qs in queries + ) #################### # CONTEXT MANAGEMENT diff --git a/test/test_store.py b/test/test_store.py index 02e13cb..5b10849 100644 --- a/test/test_store.py +++ b/test/test_store.py @@ -6,12 +6,10 @@ import rdflib from django import test -from rdflib.graph import Graph, ConjunctiveGraph +from rdflib.graph import Graph from rdflib.namespace import RDF, RDFS, Namespace from rdflib.term import BNode, Literal, URIRef -from rdflib_django.store import DjangoStore - EX = Namespace("http://www.example.com/") From bd6fa8f2add83565d9e91bfd2ddce932de380c46 Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Wed, 1 Nov 2023 15:43:14 +0100 Subject: [PATCH 6/6] Remove deprecated call to assertEquals --- test/test_utils.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index eea0194..db825f3 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -29,20 +29,20 @@ def test_conjunctive_and_named_graphs(self): g4.add((EX.x, EX.x, EX.x)) g5.add((EX.y, EX.y, EX.y)) - self.assertEquals(len(g1), 1) - self.assertEquals(len(g1_s2), 1) - self.assertEquals(len(g2), 2) - self.assertEquals(len(g3), 2) - self.assertEquals(len(g4), 2) - - self.assertEquals(set(g1), {(EX.a, EX.a, EX.a)}) - self.assertEquals(set(g2), {(EX.a, EX.a, EX.a), (EX.b, EX.b, EX.b)}) - self.assertEquals(set(g1_s2), {(EX.c, EX.c, EX.c)}) - self.assertEquals(set(g3), {(EX.c, EX.c, EX.c), (EX.d, EX.d, EX.d)}) - self.assertEquals(set(g4), set(g5)) + self.assertEqual(len(g1), 1) + self.assertEqual(len(g1_s2), 1) + self.assertEqual(len(g2), 2) + self.assertEqual(len(g3), 2) + self.assertEqual(len(g4), 2) + + self.assertEqual(set(g1), {(EX.a, EX.a, EX.a)}) + self.assertEqual(set(g2), {(EX.a, EX.a, EX.a), (EX.b, EX.b, EX.b)}) + self.assertEqual(set(g1_s2), {(EX.c, EX.c, EX.c)}) + self.assertEqual(set(g3), {(EX.c, EX.c, EX.c), (EX.d, EX.d, EX.d)}) + self.assertEqual(set(g4), set(g5)) g2.remove((None, None, None)) - self.assertEquals(list(g1), []) - self.assertEquals(list(g2), []) - self.assertEquals(list(g1_s2), [(EX.c, EX.c, EX.c)]) + self.assertEqual(list(g1), []) + self.assertEqual(list(g2), []) + self.assertEqual(list(g1_s2), [(EX.c, EX.c, EX.c)])