Files
@ 05aabe3d7b02
Branch filter:
Location: light9/light9/rdfdb/patch.py
05aabe3d7b02
5.5 KiB
text/x-python
fix some minor issues with graph contexts
Ignore-this: 7fd366a4b6cbd94af6f9edb1b4b4c6fc
Ignore-this: 7fd366a4b6cbd94af6f9edb1b4b4c6fc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | import json, unittest
from rdflib import ConjunctiveGraph, URIRef, URIRef as U
from light9.rdfdb.rdflibpatch import graphFromNQuad, graphFromQuads, serializeQuad
ALLSTMTS = (None, None, None)
class Patch(object):
"""
immutable
the json representation includes the {"patch":...} wrapper
"""
def __init__(self, jsonRepr=None, addQuads=None, delQuads=None,
addGraph=None, delGraph=None):
"""
addQuads/delQuads can be lists or sets, but if we make them internally,
they'll be lists
"""
self._jsonRepr = jsonRepr
self._addQuads, self._delQuads = addQuads, delQuads
self._addGraph, self._delGraph = addGraph, delGraph
if self._jsonRepr is not None:
body = json.loads(self._jsonRepr)
self._delGraph = graphFromNQuad(body['patch']['deletes'])
self._addGraph = graphFromNQuad(body['patch']['adds'])
if 'senderUpdateUri' in body:
self.senderUpdateUri = body['senderUpdateUri']
@classmethod
def fromDiff(cls, oldGraph, newGraph):
"""
make a patch that changes oldGraph to newGraph
"""
old = set(oldGraph.quads(ALLSTMTS))
new = set(newGraph.quads(ALLSTMTS))
return cls(addQuads=list(new - old), delQuads=list(old - new))
def __nonzero__(self):
"""
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)
@property
def addQuads(self):
if self._addQuads is None:
if self._addGraph is None:
return []
self._addQuads = list(self._addGraph.quads(ALLSTMTS))
return self._addQuads
@property
def delQuads(self):
if self._delQuads is None:
if self._delGraph is None:
return []
self._delQuads = list(self._delGraph.quads(ALLSTMTS))
return self._delQuads
@property
def addGraph(self):
if self._addGraph is None:
self._addGraph = graphFromQuads(self._addQuads)
return self._addGraph
@property
def delGraph(self):
if self._delGraph is None:
self._delGraph = graphFromQuads(self._delQuads)
return self._delGraph
@property
def jsonRepr(self):
if self._jsonRepr is None:
self._jsonRepr = self.makeJsonRepr()
return self._jsonRepr
def makeJsonRepr(self, extraAttrs={}):
d = {"patch" : {
'adds' : serializeQuad(self.addGraph),
'deletes' : serializeQuad(self.delGraph),
}}
if len(self.addGraph) > 0 and d['patch']['adds'].strip() == "":
# this is the bug that graphFromNQuad works around
raise ValueError("nquads serialization failure")
if '[<' in d['patch']['adds']:
raise ValueError("[< found in %s" % d['patch']['adds'])
d.update(extraAttrs)
return json.dumps(d)
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
for q in self.addQuads + self.delQuads:
if ctx is None:
ctx = q[3]
if ctx != q[3]:
raise ValueError("patch applies to multiple contexts, at least %r and %r" % (ctx, q[3]))
if ctx is None:
raise ValueError("patch affects no contexts")
assert isinstance(ctx, URIRef), ctx
return ctx
stmt1 = U('http://a'), U('http://b'), U('http://c'), U('http://ctx1')
class TestPatchFromDiff(unittest.TestCase):
def testEmpty(self):
g = ConjunctiveGraph()
p = Patch.fromDiff(g, g)
self.assert_(not p)
def testNonEmpty(self):
g1 = ConjunctiveGraph()
g2 = graphFromQuads([stmt1])
p = Patch.fromDiff(g1, g2)
self.assert_(p)
def testNoticesAdds(self):
g1 = ConjunctiveGraph()
g2 = graphFromQuads([stmt1])
p = Patch.fromDiff(g1, g2)
self.assertEqual(p.addQuads, [stmt1])
self.assertEqual(p.delQuads, [])
def testNoticesDels(self):
g1 = graphFromQuads([stmt1])
g2 = ConjunctiveGraph()
p = Patch.fromDiff(g1, g2)
self.assertEqual(p.addQuads, [])
self.assertEqual(p.delQuads, [stmt1])
class TestPatchGetContext(unittest.TestCase):
def testEmptyPatchCantGiveContext(self):
p = Patch()
self.assertRaises(ValueError, p.getContext)
def testSimplePatchReturnsContext(self):
p = Patch(addQuads=[stmt1])
self.assertEqual(p.getContext(), U('http://ctx1'))
def testMultiContextPatchFailsToReturnContext(self):
p = Patch(addQuads=[stmt1[:3] + (U('http://ctx1'),),
stmt1[:3] + (U('http://ctx2'),)])
self.assertRaises(ValueError, p.getContext)
|