changeset 132:453726e6f891

write compactPatches
author drewp@bigasterisk.com
date Mon, 29 May 2023 22:26:18 -0700
parents a47c8224e97f
children f11770a0a797
files rdfdb/patch.py rdfdb/patch_test.py
diffstat 2 files changed, 47 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/rdfdb/patch.py	Mon May 29 16:15:46 2023 -0700
+++ b/rdfdb/patch.py	Mon May 29 22:26:18 2023 -0700
@@ -1,6 +1,6 @@
 import itertools
 import json
-from typing import Iterable, Optional, Tuple, cast
+from typing import Iterable, Optional, Sequence, Tuple, cast
 
 from rdflib import ConjunctiveGraph, Graph, Literal, Namespace, URIRef
 
@@ -10,11 +10,11 @@
 
 ALLSTMTS = (None, None, None)
 
+
 class EmptyPatch(ValueError):
     pass
 
 
-
 def quadsWithContextUris(quads):
     """
     yield the given quads, correcting any context values that are
@@ -70,6 +70,10 @@
                 if cast(Literal, q[2]).datatype in [XSD['double'], XSD['float']]:
                     raise ValueError(f'fix creation of quad {q} to use xsd:decimal, or comparisons will fail')
 
+    def __eq__(self, other: 'Patch') -> bool:
+        # doesn't cancel out quads
+        return set(self.delQuads) == set(other.delQuads) and set(self.addQuads) == set(other.addQuads)
+
     def __str__(self):
 
         def shorten(n):
@@ -107,9 +111,7 @@
         """
         does this patch do anything to a graph?
         """
-        if self._jsonRepr and self._jsonRepr.strip():
-            raise NotImplementedError()
-        return bool(self._addQuads or self._delQuads or self._addGraph or self._delGraph)
+        return not self.isEmpty()
 
     @property
     def addQuads(self):
@@ -170,27 +172,6 @@
             return self
         return Patch(addQuads=adds - both, delQuads=dels - both)
 
-    def concat(self, more):
-        """
-        new Patch with the result of applying this patch and the
-        sequence of other Patches
-        """
-        # not working yet
-        adds = set(self.addQuads)
-        dels = set(self.delQuads)
-        for p2 in more:
-            for q in p2.delQuads:
-                if q in adds:
-                    adds.remove(q)
-                else:
-                    dels.add(q)
-            for q in p2.addQuads:
-                if q in dels:
-                    dels.remove(q)
-                else:
-                    adds.add(q)
-        return Patch(delQuads=dels, addQuads=adds)
-
     def getContext(self):
         """assumes that all the edits are on the same context"""
         ctx = None
@@ -207,3 +188,20 @@
 
     def isEmpty(self):
         return set(self.addQuads) == set(self.delQuads)
+
+    def update(self, other: 'Patch') -> 'Patch':
+        return Patch(delQuads=set(self.delQuads).union(other.delQuads), addQuads=set(self.addQuads).union(other.addQuads))
+
+
+def compactPatches(ps: Sequence[Patch]) -> Sequence[Patch]:
+    if not ps:
+        return ps
+    combined = ps[0]
+    for p in ps[1:]:
+        combined = combined.update(p)
+    dels = set(combined.delQuads)
+    adds = set(combined.addQuads)
+    inter = dels.intersection(adds)
+    dels.difference_update(inter)
+    adds.difference_update(inter)
+    return [Patch(delQuads=dels, addQuads=adds)]
\ No newline at end of file
--- a/rdfdb/patch_test.py	Mon May 29 16:15:46 2023 -0700
+++ b/rdfdb/patch_test.py	Mon May 29 22:26:18 2023 -0700
@@ -3,10 +3,12 @@
 from rdflib import ConjunctiveGraph
 from rdflib import URIRef as U
 
-from rdfdb.patch import Patch
+from rdfdb.patch import Patch, compactPatches
 from rdfdb.rdflibpatch import graphFromQuads
 
 stmt1 = U('http://a'), U('http://b'), U('http://c'), U('http://ctx1')
+stmt2 = U('http://a'), U('http://b'), U('http://c2'), U('http://ctx1')
+stmt3 = U('http://a'), U('http://b'), U('http://c3'), U('http://ctx1')
 
 
 class TestPatchFromDiff(unittest.TestCase):
@@ -57,7 +59,26 @@
         p = Patch(addQuads=[stmt1[:3] + (U('http://ctx1'),), stmt1[:3] + (U('http://ctx2'),)])
         self.assertRaises(ValueError, p.getContext)
 
+
 class TestPatchDoesntConsumeIterators:
+
     def test(self):
         p = Patch(addQuads=iter(stmt1))
-        assert not p.isEmpty()
\ No newline at end of file
+        assert not p.isEmpty()
+
+
+class TestCompactPatches:
+
+    def testHalfPatchesMerge(self):
+        assert (compactPatches([
+            Patch(delQuads=[stmt1]),  #
+            Patch(addQuads=[stmt2])
+        ]) == [Patch(delQuads=[stmt1], addQuads=[stmt2])])
+
+    def testTypicalEditsOfObject(self):
+        step1 = Patch(delQuads=[stmt1], addQuads=[stmt2])
+        step2 = Patch(delQuads=[stmt2], addQuads=[stmt3])
+        assert compactPatches([step1, step2]) == [Patch(delQuads=[stmt1], addQuads=[stmt3])]
+
+    def testEmpty(self):
+        assert compactPatches([]) == []
\ No newline at end of file