use jsonpointer, update to current spec draft
This commit is contained in:
75
jsonpatch.py
75
jsonpatch.py
@@ -47,10 +47,14 @@ if sys.version_info < (2, 6):
|
||||
else:
|
||||
import json
|
||||
|
||||
import jsonpointer
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
basestring = (bytes, str)
|
||||
|
||||
|
||||
JsonPointerException = jsonpointer.JsonPointerException
|
||||
|
||||
class JsonPatchException(Exception):
|
||||
"""Base Json Patch exception"""
|
||||
|
||||
@@ -321,50 +325,13 @@ class PatchOperation(object):
|
||||
|
||||
def __init__(self, operation):
|
||||
self.location = operation['path']
|
||||
self.pointer = jsonpointer.JsonPointer(self.location)
|
||||
self.operation = operation
|
||||
|
||||
def apply(self, obj):
|
||||
"""Abstract method that applies patch operation to specified object."""
|
||||
raise NotImplementedError('should implement patch operation.')
|
||||
|
||||
def locate(self, obj, location, last_must_exist=True):
|
||||
"""Walks through the object according to location.
|
||||
|
||||
Returns the last step as (sub-object, last location-step)."""
|
||||
|
||||
parts = location.split('/')
|
||||
if parts.pop(0) != '':
|
||||
raise JsonPatchException('location must starts with /')
|
||||
|
||||
for part in parts[:-1]:
|
||||
obj, _ = self._step(obj, part)
|
||||
|
||||
_, last_loc = self._step(obj, parts[-1], must_exist=last_must_exist)
|
||||
return obj, last_loc
|
||||
|
||||
def _step(self, obj, loc_part, must_exist=True):
|
||||
"""Goes one step in a locate() call."""
|
||||
|
||||
if isinstance(obj, dict):
|
||||
part_variants = [loc_part]
|
||||
for variant in part_variants:
|
||||
if variant not in obj:
|
||||
continue
|
||||
return obj[variant], variant
|
||||
elif isinstance(obj, list):
|
||||
part_variants = [int(loc_part)]
|
||||
for variant in part_variants:
|
||||
if variant >= len(obj):
|
||||
continue
|
||||
return obj[variant], variant
|
||||
else:
|
||||
raise ValueError('list or dict expected, got %r' % type(obj))
|
||||
|
||||
if must_exist:
|
||||
raise JsonPatchConflict('key %s not found' % loc_part)
|
||||
else:
|
||||
return obj, part_variants[0]
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
return hash(frozenset(self.operation.items()))
|
||||
@@ -381,7 +348,7 @@ class RemoveOperation(PatchOperation):
|
||||
"""Removes an object property or an array element."""
|
||||
|
||||
def apply(self, obj):
|
||||
subobj, part = self.locate(obj, self.location)
|
||||
subobj, part = self.pointer.to_last(obj)
|
||||
del subobj[part]
|
||||
|
||||
|
||||
@@ -390,13 +357,18 @@ class AddOperation(PatchOperation):
|
||||
|
||||
def apply(self, obj):
|
||||
value = self.operation["value"]
|
||||
subobj, part = self.locate(obj, self.location, last_must_exist=False)
|
||||
subobj, part = self.pointer.to_last(obj, None)
|
||||
|
||||
if isinstance(subobj, list):
|
||||
if part > len(subobj) or part < 0:
|
||||
|
||||
if part == '-':
|
||||
subobj.append(value)
|
||||
|
||||
elif part > len(subobj) or part < 0:
|
||||
raise JsonPatchConflict("can't insert outside of list")
|
||||
|
||||
subobj.insert(part, value)
|
||||
else:
|
||||
subobj.insert(part, value)
|
||||
|
||||
elif isinstance(subobj, dict):
|
||||
if part in subobj:
|
||||
@@ -414,7 +386,7 @@ class ReplaceOperation(PatchOperation):
|
||||
|
||||
def apply(self, obj):
|
||||
value = self.operation["value"]
|
||||
subobj, part = self.locate(obj, self.location)
|
||||
subobj, part = self.pointer.to_last(obj)
|
||||
|
||||
if isinstance(subobj, list):
|
||||
if part > len(subobj) or part < 0:
|
||||
@@ -436,9 +408,14 @@ class MoveOperation(PatchOperation):
|
||||
"""Moves an object property or an array element to new location."""
|
||||
|
||||
def apply(self, obj):
|
||||
subobj, part = self.locate(obj, self.location)
|
||||
subobj, part = self.pointer.to_last(obj)
|
||||
value = subobj[part]
|
||||
|
||||
to_ptr = jsonpointer.JsonPointer(self.operation['to'])
|
||||
|
||||
if self.pointer.contains(to_ptr):
|
||||
raise JsonPatchException('Cannot move values into its own children')
|
||||
|
||||
RemoveOperation({'op': 'remove', 'path': self.location}).apply(obj)
|
||||
AddOperation({'op': 'add', 'path': self.operation['to'], 'value': value}).apply(obj)
|
||||
|
||||
@@ -448,8 +425,10 @@ class TestOperation(PatchOperation):
|
||||
|
||||
def apply(self, obj):
|
||||
try:
|
||||
subobj, part = self.locate(obj, self.location)
|
||||
except JsonPatchConflict:
|
||||
subobj, part = self.pointer.to_last(obj)
|
||||
val = self.pointer.walk(subobj, part)
|
||||
|
||||
except JsonPointerException:
|
||||
exc_info = sys.exc_info()
|
||||
exc = JsonPatchTestFailed(str(exc_info[1]))
|
||||
if sys.version_info >= (3, 0):
|
||||
@@ -457,8 +436,6 @@ class TestOperation(PatchOperation):
|
||||
else:
|
||||
raise exc
|
||||
|
||||
val = subobj[part]
|
||||
|
||||
if 'value' in self.operation:
|
||||
value = self.operation['value']
|
||||
if val != value:
|
||||
@@ -469,6 +446,6 @@ class CopyOperation(PatchOperation):
|
||||
""" Copies an object property or an array element to a new location """
|
||||
|
||||
def apply(self, obj):
|
||||
subobj, part = self.locate(obj, self.location)
|
||||
subobj, part = self.pointer.to_last(obj)
|
||||
value = copy.deepcopy(subobj[part])
|
||||
AddOperation({'op': 'add', 'path': self.operation['to'], 'value': value}).apply(obj)
|
||||
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
jsonpointer>=0.5
|
||||
14
tests.py
14
tests.py
@@ -136,6 +136,20 @@ class ApplyPatchTestCase(unittest.TestCase):
|
||||
obj, [{'op': 'test', 'path': '/baz/qx'}])
|
||||
|
||||
|
||||
def test_unrecognized_element(self):
|
||||
obj = {'foo': 'bar', 'baz': 'qux'}
|
||||
res = jsonpatch.apply_patch(obj, [{'op': 'replace', 'path': '/baz', 'value': 'boo', 'foo': 'ignore'}])
|
||||
self.assertTrue(res['baz'], 'boo')
|
||||
|
||||
|
||||
def test_append(self):
|
||||
obj = {'foo': [1, 2]}
|
||||
res = jsonpatch.apply_patch(obj, [
|
||||
{'op': 'add', 'path': '/foo/-', 'value': 3},
|
||||
{'op': 'add', 'path': '/foo/-', 'value': 4},
|
||||
])
|
||||
|
||||
|
||||
|
||||
class EqualityTestCase(unittest.TestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user