Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 19 additions & 51 deletions jsonpatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ class RemoveOperation(PatchOperation):
"""Removes an object property or an array element."""

def apply(self, obj):
subobj, part = _to_last(self.pointer, obj)
subobj, part = self.pointer.to_last(obj)

if part is None:
raise JsonPatchConflict("can't remove the whole document")
Expand Down Expand Up @@ -281,7 +281,7 @@ def apply(self, obj):
return self._add(obj, copy.deepcopy(value))

def _add(self, obj, value):
subobj, part = _to_last(self.pointer, obj)
subobj, part = self.pointer.to_last(obj)

if part is None:
return value # we're replacing the root, whatever its type
Expand Down Expand Up @@ -317,7 +317,7 @@ def apply(self, obj):
# copied for the same reason as in AddOperation.apply
value = copy.deepcopy(value)

subobj, part = _to_last(self.pointer, obj)
subobj, part = self.pointer.to_last(obj)

if part is None:
return value
Expand Down Expand Up @@ -413,7 +413,7 @@ class TestOperation(PatchOperation):

def apply(self, obj):
try:
subobj, part = _to_last(self.pointer, obj)
subobj, part = self.pointer.to_last(obj)
if part is None:
val = subobj
else:
Expand Down Expand Up @@ -836,19 +836,18 @@ def _item_removed(self, path, key, item):
if index is not None:
added_location = index[2]['path']
added = self._parts(added_location)
moved_from = _without_item(source, added)
target = _item_after(added, source, False)
# RFC 6902 does not allow moving a value into its own children
if not _is_inside(target, moved_from):
self.remove(index)
if moved_from != target:
self._set_present(location, False)
self.insert({'op': 'move', 'from': location,
'path': added_location})
else:
# the removed item stays where the added one would be
self._set_present(added_location, False)
return
# The documents are compared in order, so the added item is in
# the part compared already: it is in front of the removed one,
# and neither inside it nor moved by removing it
self.remove(index)
if _without_item(source, added) != added:
self._set_present(location, False)
self.insert({'op': 'move', 'from': location,
'path': added_location})
else:
# the removed item stays where the added one would be
self._set_present(added_location, False)
return

self._set_present(location, False)
new_index = self.insert({'op': 'remove', 'path': location})
Expand Down Expand Up @@ -1186,44 +1185,13 @@ def _without_item(parts, location):
return parts


def _item_after(location, parts, inserted):
""" Where the item at location is after inserting (or removing, if not
inserted) the value at parts """
depth = len(parts) - 1
if not isinstance(parts[-1], int) or not _is_inside(location, parts[:-1]):
return location

if inserted and location[depth] >= parts[-1]:
return _shift(location, depth, 1)

if not inserted and location[depth] > parts[-1]:
return _shift(location, depth, -1)

return location


def _to_last(pointer, doc):
"""Resolve pointer like JsonPointer.to_last, without indexing into strings.

RFC 6901 only allows reference tokens to be applied to objects and arrays,
but older versions of jsonpointer treat strings as sequences.
"""
subobj, part = pointer.to_last(doc)

if part is not None and isinstance(subobj, str):
raise JsonPointerException(
"Cannot apply token '{0}' to non-container type {1}".format(
part, type(subobj)))

return subobj, part


def _to_existing_last(pointer, doc):
"""Resolve pointer like _to_last, for a location that has to exist.
"""Resolve pointer like JsonPointer.to_last, for a location that has to
exist.

The '-' of an array refers to the nonexistent element after its last one.
"""
subobj, part = _to_last(pointer, doc)
subobj, part = pointer.to_last(doc)

if part == '-' and isinstance(subobj, Sequence):
raise JsonPointerException("invalid array index '{0}'".format(part))
Expand Down
Loading