Browse files

use jsonpointer, update to current spec draft

  • Loading branch information...
1 parent 14b239c commit ae11875d59b4c7ddb93e5d027adf13aa4bfed670 @stefankoegl committed Nov 15, 2012
Showing with 41 additions and 49 deletions.
  1. +26 −49 jsonpatch.py
  2. +1 −0 requirements.txt
  3. +14 −0 tests.py
View
75 jsonpatch.py
@@ -47,10 +47,14 @@
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,17 +425,17 @@ 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):
raise exc.with_traceback(exc_info[2])
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)
View
1 requirements.txt
@@ -0,0 +1 @@
+jsonpointer>=0.5
View
14 tests.py
@@ -136,6 +136,20 @@ def test_test_noval_not_existing_nested(self):
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},
+ ])
+
@kxepal
kxepal added a line comment Dec 17, 2012

probably missed:

self.assertEqual(res['foo'], [1, 2, 3, 4]) 
@stefankoegl
Owner
stefankoegl added a line comment Dec 17, 2012

fixed in 3f9bd95, thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
class EqualityTestCase(unittest.TestCase):

0 comments on commit ae11875

Please sign in to comment.