Add make_patch function to generate JsonPatch by comparing of two documents.
This commit is contained in:
48
jsonpatch.py
48
jsonpatch.py
@@ -70,6 +70,54 @@ def apply_patch(doc, patch):
|
|||||||
patch = JsonPatch(patch)
|
patch = JsonPatch(patch)
|
||||||
return patch.apply(doc)
|
return patch.apply(doc)
|
||||||
|
|
||||||
|
def make_patch(src, dst):
|
||||||
|
"""Generates patch by comparing of two objects.
|
||||||
|
|
||||||
|
>>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]}
|
||||||
|
>>> dst = {'baz': 'qux', 'numbers': [1, 4, 7]}
|
||||||
|
>>> patch = make_patch(src, dst)
|
||||||
|
>>> patch.apply(src) #doctest: +ELLIPSIS
|
||||||
|
{...}
|
||||||
|
>>> src == dst
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
def compare_values(path, value, other):
|
||||||
|
if isinstance(value, dict) and isinstance(other, dict):
|
||||||
|
for operation in compare_dict(path, value, other):
|
||||||
|
yield operation
|
||||||
|
elif isinstance(value, list) and isinstance(other, list):
|
||||||
|
for operation in compare_list(path, value, other):
|
||||||
|
yield operation
|
||||||
|
else:
|
||||||
|
yield {'replace': '/'.join(path), 'value': other}
|
||||||
|
|
||||||
|
def compare_dict(path, src, dst):
|
||||||
|
for key in src:
|
||||||
|
if key not in dst:
|
||||||
|
yield {'remove': '/'.join(path + [key])}
|
||||||
|
elif src[key] != dst[key]:
|
||||||
|
current = path + [key]
|
||||||
|
for operation in compare_values(current, src[key], dst[key]):
|
||||||
|
yield operation
|
||||||
|
for key in dst:
|
||||||
|
if key not in src:
|
||||||
|
yield {'add': '/'.join(path + [key]), 'value': dst[key]}
|
||||||
|
|
||||||
|
def compare_list(path, src, dst):
|
||||||
|
lsrc, ldst = len(src), len(dst)
|
||||||
|
for idx in reversed(xrange(max(lsrc, ldst))):
|
||||||
|
if idx < lsrc and idx < ldst:
|
||||||
|
current = path + [str(idx)]
|
||||||
|
for operation in compare_values(current, src[idx], dst[idx]):
|
||||||
|
yield operation
|
||||||
|
elif idx < ldst:
|
||||||
|
yield {'add': '/'.join(path + [str(idx)]),
|
||||||
|
'value': dst[idx]}
|
||||||
|
elif idx < lsrc:
|
||||||
|
yield {'remove': '/'.join(path + [str(idx)])}
|
||||||
|
|
||||||
|
return JsonPatch(list(compare_dict([''], src, dst)))
|
||||||
|
|
||||||
|
|
||||||
class JsonPatch(object):
|
class JsonPatch(object):
|
||||||
"""A JSON Patch is a list of Patch Operations.
|
"""A JSON Patch is a list of Patch Operations.
|
||||||
|
|||||||
29
tests.py
29
tests.py
@@ -63,10 +63,39 @@ class ApplyPatchTestCase(unittest.TestCase):
|
|||||||
obj, [{'test': '/bar', 'value': 'bar'}])
|
obj, [{'test': '/bar', 'value': 'bar'}])
|
||||||
|
|
||||||
|
|
||||||
|
class MakePatchTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_objects(self):
|
||||||
|
src = {'foo': 'bar', 'boo': 'qux'}
|
||||||
|
dst = {'baz': 'qux', 'foo': 'boo'}
|
||||||
|
patch = jsonpatch.make_patch(src, dst)
|
||||||
|
patch.apply(src)
|
||||||
|
self.assertEqual(src, dst)
|
||||||
|
|
||||||
|
def test_arrays(self):
|
||||||
|
src = {'numbers': [1, 2, 3], 'other': [1, 3, 4, 5]}
|
||||||
|
dst = {'numbers': [1, 3, 4, 5], 'other': [1, 3, 4]}
|
||||||
|
patch = jsonpatch.make_patch(src, dst)
|
||||||
|
patch.apply(src)
|
||||||
|
self.assertEqual(src, dst)
|
||||||
|
|
||||||
|
def test_complex_object(self):
|
||||||
|
src = {'data': [
|
||||||
|
{'foo': 1}, {'bar': [1, 2, 3]}, {'baz': {'1': 1, '2': 2}}
|
||||||
|
]}
|
||||||
|
dst = {'data': [
|
||||||
|
{'foo': [42]}, {'bar': []}, {'baz': {'boo': 'oom!'}}
|
||||||
|
]}
|
||||||
|
patch = jsonpatch.make_patch(src, dst)
|
||||||
|
patch.apply(src)
|
||||||
|
self.assertEqual(src, dst)
|
||||||
|
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
suite.addTest(doctest.DocTestSuite(jsonpatch))
|
suite.addTest(doctest.DocTestSuite(jsonpatch))
|
||||||
suite.addTest(unittest.makeSuite(ApplyPatchTestCase))
|
suite.addTest(unittest.makeSuite(ApplyPatchTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(MakePatchTestCase))
|
||||||
return suite
|
return suite
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user