Add docstrings and fix code style in favour to pylint.
This commit is contained in:
55
jsonpatch.py
55
jsonpatch.py
@@ -30,7 +30,8 @@
|
|||||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#
|
#
|
||||||
|
|
||||||
"""Apply JSON-Patches according to http://tools.ietf.org/html/draft-pbryan-json-patch-04"""
|
"""Apply JSON-Patches according to
|
||||||
|
http://tools.ietf.org/html/draft-pbryan-json-patch-04"""
|
||||||
|
|
||||||
# Will be parsed by setup.py to determine package metadata
|
# Will be parsed by setup.py to determine package metadata
|
||||||
__author__ = 'Stefan Kögl <stefan@skoegl.net>'
|
__author__ = 'Stefan Kögl <stefan@skoegl.net>'
|
||||||
@@ -43,11 +44,16 @@ import json
|
|||||||
|
|
||||||
|
|
||||||
class JsonPatchException(Exception):
|
class JsonPatchException(Exception):
|
||||||
pass
|
"""Base Json Patch exception"""
|
||||||
|
|
||||||
|
|
||||||
class JsonPatchConflict(JsonPatchException):
|
class JsonPatchConflict(JsonPatchException):
|
||||||
pass
|
"""Raises if patch could be applied due to conflict situations such as:
|
||||||
|
- attempt to add object key then it already exists;
|
||||||
|
- attempt to operate with nonexistence object key;
|
||||||
|
- attempt to insert value to array at position beyond of it size;
|
||||||
|
- etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def apply_patch(doc, patch):
|
def apply_patch(doc, patch):
|
||||||
@@ -61,8 +67,8 @@ def apply_patch(doc, patch):
|
|||||||
{'foo': 'bar', 'baz': 'qux'}
|
{'foo': 'bar', 'baz': 'qux'}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
p = JsonPatch(patch)
|
patch = JsonPatch(patch)
|
||||||
return p.apply(doc)
|
return patch.apply(doc)
|
||||||
|
|
||||||
|
|
||||||
class JsonPatch(object):
|
class JsonPatch(object):
|
||||||
@@ -84,7 +90,7 @@ class JsonPatch(object):
|
|||||||
def __init__(self, patch):
|
def __init__(self, patch):
|
||||||
self.patch = patch
|
self.patch = patch
|
||||||
|
|
||||||
self.OPERATIONS = {
|
self.operations = {
|
||||||
'remove': RemoveOperation,
|
'remove': RemoveOperation,
|
||||||
'add': AddOperation,
|
'add': AddOperation,
|
||||||
'replace': ReplaceOperation,
|
'replace': ReplaceOperation,
|
||||||
@@ -92,34 +98,30 @@ class JsonPatch(object):
|
|||||||
'test': TestOperation
|
'test': TestOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_string(cls, patch_str):
|
def from_string(cls, patch_str):
|
||||||
|
"""Creates JsonPatch instance from string source."""
|
||||||
patch = json.loads(patch_str)
|
patch = json.loads(patch_str)
|
||||||
return cls(patch)
|
return cls(patch)
|
||||||
|
|
||||||
|
|
||||||
def apply(self, obj):
|
def apply(self, obj):
|
||||||
""" Applies the patch to given object """
|
"""Applies the patch to given object."""
|
||||||
|
|
||||||
for operation in self.patch:
|
for operation in self.patch:
|
||||||
op = self._get_operation(operation)
|
operation = self._get_operation(operation)
|
||||||
op.apply(obj)
|
operation.apply(obj)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def _get_operation(self, operation):
|
def _get_operation(self, operation):
|
||||||
for action, op_cls in self.OPERATIONS.items():
|
for action, op_cls in self.operations.items():
|
||||||
if action in operation:
|
if action in operation:
|
||||||
location = operation[action]
|
location = operation[action]
|
||||||
op = op_cls(location, operation)
|
return op_cls(location, operation)
|
||||||
return op
|
|
||||||
|
|
||||||
raise JsonPatchException("invalid operation '%s'" % operation)
|
raise JsonPatchException("invalid operation '%s'" % operation)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PatchOperation(object):
|
class PatchOperation(object):
|
||||||
"""A single operation inside a JSON Patch."""
|
"""A single operation inside a JSON Patch."""
|
||||||
|
|
||||||
@@ -127,25 +129,27 @@ class PatchOperation(object):
|
|||||||
self.location = location
|
self.location = location
|
||||||
self.operation = operation
|
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):
|
def locate(self, obj, location, last_must_exist=True):
|
||||||
""" Walks through the object according to location
|
"""Walks through the object according to location.
|
||||||
|
|
||||||
Returns the last step as (sub-object, last location-step) """
|
Returns the last step as (sub-object, last location-step)."""
|
||||||
|
|
||||||
parts = location.split('/')
|
parts = location.split('/')
|
||||||
if parts.pop(0) != '':
|
if parts.pop(0) != '':
|
||||||
raise JsonPatchException('location must starts with /')
|
raise JsonPatchException('location must starts with /')
|
||||||
|
|
||||||
for part in parts[:-1]:
|
for part in parts[:-1]:
|
||||||
obj, loc_part = self._step(obj, part)
|
obj, _ = self._step(obj, part)
|
||||||
|
|
||||||
_, last_loc = self._step(obj, parts[-1], must_exist=last_must_exist)
|
_, last_loc = self._step(obj, parts[-1], must_exist=last_must_exist)
|
||||||
return obj, last_loc
|
return obj, last_loc
|
||||||
|
|
||||||
|
|
||||||
def _step(self, obj, loc_part, must_exist=True):
|
def _step(self, obj, loc_part, must_exist=True):
|
||||||
""" Goes one step in a locate() call """
|
"""Goes one step in a locate() call."""
|
||||||
|
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
part_variants = [loc_part]
|
part_variants = [loc_part]
|
||||||
@@ -196,7 +200,8 @@ class AddOperation(PatchOperation):
|
|||||||
subobj[part] = value
|
subobj[part] = value
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise JsonPatchConflict("can't add to type '%s'" % subobj.__class__.__name__)
|
raise JsonPatchConflict("can't add to type '%s'"
|
||||||
|
"" % subobj.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
class ReplaceOperation(PatchOperation):
|
class ReplaceOperation(PatchOperation):
|
||||||
@@ -212,10 +217,12 @@ class ReplaceOperation(PatchOperation):
|
|||||||
|
|
||||||
elif isinstance(subobj, dict):
|
elif isinstance(subobj, dict):
|
||||||
if not part in subobj:
|
if not part in subobj:
|
||||||
raise JsonPatchConflict("can't replace non-existant object '%s'" % part)
|
raise JsonPatchConflict("can't replace non-existant object '%s'"
|
||||||
|
"" % part)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise JsonPatchConflict("can't replace in type '%s'" % subobj.__class__.__name__)
|
raise JsonPatchConflict("can't replace in type '%s'"
|
||||||
|
"" % subobj.__class__.__name__)
|
||||||
|
|
||||||
subobj[part] = value
|
subobj[part] = value
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user