diff --git a/jsonpatch.py b/jsonpatch.py index 14b2162..494d42d 100644 --- a/jsonpatch.py +++ b/jsonpatch.py @@ -51,11 +51,14 @@ class JsonPatchConflict(JsonPatchException): def apply_patch(doc, patch): - """ - >>> obj = { 'baz': 'qux', 'foo': 'bar' } - >>> patch = [ { 'remove': '/baz' } ] - >>> apply_patch(obj, patch) - {'foo': 'bar'} + """ Apply list of patches to specified json document. + + >>> doc = {'foo': 'bar'} + >>> other = apply_patch(doc, [{'add': '/baz', 'value': 'qux'}]) + >>> doc is other + True + >>> doc + {'foo': 'bar', 'baz': 'qux'} """ p = JsonPatch(patch) @@ -63,7 +66,20 @@ def apply_patch(doc, patch): class JsonPatch(object): - """ A JSON Patch is a list of Patch Operations """ + """ A JSON Patch is a list of Patch Operations. + + >>> patch = JsonPatch([ + ... {'add': '/foo', 'value': 'bar'}, + ... {'add': '/baz', 'value': [1, 2, 3]}, + ... {'remove': '/baz/1'}, + ... {'test': '/baz', 'value': [1, 3]}, + ... {'replace': '/baz/0', 'value': 42}, + ... {'remove': '/baz/1'}, + ... ]) + >>> doc = {} + >>> patch.apply(doc) + {'foo': 'bar', 'baz': [42]} + """ def __init__(self, patch): self.patch = patch @@ -105,7 +121,7 @@ class JsonPatch(object): class PatchOperation(object): - """ A single operation inside a JSON Patch """ + """ A single operation inside a JSON Patch. """ def __init__(self, location, operation): self.location = location @@ -153,18 +169,7 @@ class PatchOperation(object): class RemoveOperation(PatchOperation): - """ Removes an object property or an array element - - >>> obj = { 'baz': 'qux', 'foo': 'bar' } - >>> patch = JsonPatch( [ { 'remove': '/baz' } ] ) - >>> patch.apply(obj) - {'foo': 'bar'} - - >>> obj = { 'foo': [ 'bar', 'qux', 'baz' ] } - >>> patch = JsonPatch( [ { "remove": "/foo/1" } ] ) - >>> patch.apply(obj) - {'foo': ['bar', 'baz']} - """ + """ Removes an object property or an array element. """ def apply(self, obj): subobj, part = self.locate(obj, self.location) @@ -172,18 +177,7 @@ class RemoveOperation(PatchOperation): class AddOperation(PatchOperation): - """ Adds an object property or an array element - - >>> obj = { "foo": "bar" } - >>> patch = JsonPatch([ { "add": "/baz", "value": "qux" } ]) - >>> patch.apply(obj) - {'foo': 'bar', 'baz': 'qux'} - - >>> obj = { "foo": [ "bar", "baz" ] } - >>> patch = JsonPatch([ { "add": "/foo/1", "value": "qux" } ]) - >>> patch.apply(obj) - {'foo': ['bar', 'qux', 'baz']} - """ + """ Adds an object property or an array element. """ def apply(self, obj): value = self.operation["value"] @@ -206,13 +200,7 @@ class AddOperation(PatchOperation): class ReplaceOperation(PatchOperation): - """ Replaces a value - - >>> obj = { "baz": "qux", "foo": "bar" } - >>> patch = JsonPatch([ { "replace": "/baz", "value": "boo" } ]) - >>> patch.apply(obj) - {'foo': 'bar', 'baz': 'boo'} - """ + """ Replaces an object property or an array element by new value. """ def apply(self, obj): value = self.operation["value"] @@ -233,18 +221,7 @@ class ReplaceOperation(PatchOperation): class MoveOperation(PatchOperation): - """ Moves a value - - >>> obj = {'foo': {'bar': 'baz', 'waldo': 'fred'}, 'qux': {'corge': 'grault'}} - >>> patch = JsonPatch([{'move': '/foo/waldo', 'to': '/qux/thud'}]) - >>> patch.apply(obj) - {'qux': {'thud': 'fred', 'corge': 'grault'}, 'foo': {'bar': 'baz'}} - - >>> obj = {'foo': ['all', 'grass', 'cows', 'eat']} - >>> patch = JsonPatch([{'move': '/foo/1', 'to': '/foo/3'}]) - >>> patch.apply(obj) - {'foo': ['all', 'cows', 'eat', 'grass']} - """ + """ Moves an object property or an array element to new location. """ def apply(self, obj): subobj, part = self.locate(obj, self.location) @@ -254,26 +231,7 @@ class MoveOperation(PatchOperation): class TestOperation(PatchOperation): - """ Test value by specified location - - >>> obj = {'baz': 'qux', 'foo': ['a', 2, 'c']} - >>> patch = JsonPatch([ - ... {'test': '/baz', 'value': 'qux'}, - ... {'test': '/foo/1', 'value': 2} - ... ]) - >>> patch.apply(obj) - {'foo': ['a', 2, 'c'], 'baz': 'qux'} - - >>> patch = JsonPatch([ - ... {'test': '/foo/1', 'value': 'BOOM!'} - ... ]) - >>> try: - ... patch.apply(obj) - ... except AssertionError: - ... pass - ... else: - ... assert False, 'test should fall' - """ + """ Test value by specified location. """ def apply(self, obj): value = self.operation['value'] diff --git a/tests.py b/tests.py index 5b1d17c..e958576 100755 --- a/tests.py +++ b/tests.py @@ -1,44 +1,73 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import print_function import doctest import unittest -import sys +import jsonpatch -modules = ['jsonpatch'] -coverage_modules = [] -suite = unittest.TestSuite() +class ApplyPatchTestCase(unittest.TestCase): -for module in modules: - m = __import__(module, fromlist=[module]) - coverage_modules.append(m) - suite.addTest(doctest.DocTestSuite(m)) + def test_add_object_key(self): + obj = {'foo': 'bar'} + jsonpatch.apply_patch(obj, [{'add': '/baz', 'value': 'qux'}]) + self.assertTrue('baz' in obj) + self.assertEqual(obj['baz'], 'qux') -runner = unittest.TextTestRunner(verbosity=2) + def test_add_array_item(self): + obj = {'foo': ['bar', 'baz']} + jsonpatch.apply_patch(obj, [{'add': '/foo/1', 'value': 'qux'}]) + self.assertEqual(obj['foo'], ['bar', 'qux', 'baz']) -try: - import coverage -except ImportError: - coverage = None + def test_remove_object_key(self): + obj = {'foo': 'bar', 'baz': 'qux'} + jsonpatch.apply_patch(obj, [{'remove': '/baz'}]) + self.assertTrue('baz' not in obj) -if coverage is not None: - coverage.erase() - coverage.start() + def test_remove_array_item(self): + obj = {'foo': ['bar', 'qux', 'baz']} + jsonpatch.apply_patch(obj, [{'remove': '/foo/1'}]) + self.assertEqual(obj['foo'], ['bar', 'baz']) -result = runner.run(suite) + def test_replace_object_key(self): + obj = {'foo': 'bar', 'baz': 'qux'} + jsonpatch.apply_patch(obj, [{'replace': '/baz', 'value': 'boo'}]) + self.assertTrue(obj['baz'], 'boo') -if not result.wasSuccessful(): - sys.exit(1) + def test_replace_array_item(self): + obj = {'foo': ['bar', 'qux', 'baz']} + jsonpatch.apply_patch(obj, [{'replace': '/foo/1', 'value': 'boo'}]) + self.assertEqual(obj['foo'], ['bar', 'boo', 'baz']) -if coverage is not None: - coverage.stop() - coverage.report(coverage_modules) - coverage.erase() + def test_move_object_key(self): + obj = {'foo': {'bar': 'baz', 'waldo': 'fred'}, + 'qux': {'corge': 'grault'}} + jsonpatch.apply_patch(obj, [{'move': '/foo/waldo', 'to': '/qux/thud'}]) + self.assertEqual(obj, {'qux': {'thud': 'fred', 'corge': 'grault'}, + 'foo': {'bar': 'baz'}}) -if coverage is None: - print(""" - No coverage reporting done (Python module "coverage" is missing) - Please install the python-coverage package to get coverage reporting. - """, file=sys.stderr) + def test_move_array_item(self): + obj = {'foo': ['all', 'grass', 'cows', 'eat']} + jsonpatch.apply_patch(obj, [{'move': '/foo/1', 'to': '/foo/3'}]) + self.assertEqual(obj, {'foo': ['all', 'cows', 'eat', 'grass']}) + + def test_test_success(self): + obj = {'baz': 'qux', 'foo': ['a', 2, 'c']} + jsonpatch.apply_patch(obj, [{'test': '/baz', 'value': 'qux'}, + {'test': '/foo/1', 'value': 2}]) + + def test_test_error(self): + obj = {'bar': 'qux'} + self.assertRaises(AssertionError, + jsonpatch.apply_patch, + obj, [{'test': '/bar', 'value': 'bar'}]) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(jsonpatch)) + suite.addTest(unittest.makeSuite(ApplyPatchTestCase)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite')