From ab775d187539c85cb7214905ad295358b240af14 Mon Sep 17 00:00:00 2001 From: Artyom Nikitin Date: Fri, 13 Nov 2020 00:21:07 +0300 Subject: feat: add custom json pointer support --- jsonpatch.py | 76 +++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/jsonpatch.py b/jsonpatch.py index 7d5489a..c893bea 100644 --- a/jsonpatch.py +++ b/jsonpatch.py @@ -106,7 +106,7 @@ def multidict(ordered_pairs): _jsonloads = functools.partial(json.loads, object_pairs_hook=multidict) -def apply_patch(doc, patch, in_place=False): +def apply_patch(doc, patch, in_place=False, pointer_cls=JsonPointer): """Apply list of patches to specified json document. :param doc: Document object. @@ -137,13 +137,13 @@ def apply_patch(doc, patch, in_place=False): """ if isinstance(patch, basestring): - patch = JsonPatch.from_string(patch) + patch = JsonPatch.from_string(patch, pointer_cls=pointer_cls) else: - patch = JsonPatch(patch) + patch = JsonPatch(patch, pointer_cls=pointer_cls) return patch.apply(doc, in_place) -def make_patch(src, dst): +def make_patch(src, dst, pointer_cls=JsonPointer): """Generates patch by comparing two document objects. Actually is a proxy to :meth:`JsonPatch.from_diff` method. @@ -153,6 +153,9 @@ def make_patch(src, dst): :param dst: Data source document object. :type dst: dict + :param pointer_cls: JSON pointer (sub)class. + :type pointer_cls: Type[JsonPointer] + >>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]} >>> dst = {'baz': 'qux', 'numbers': [1, 4, 7]} >>> patch = make_patch(src, dst) @@ -161,7 +164,7 @@ def make_patch(src, dst): True """ - return JsonPatch.from_diff(src, dst) + return JsonPatch.from_diff(src, dst, pointer_cls=pointer_cls) class JsonPatch(object): @@ -210,8 +213,9 @@ class JsonPatch(object): ... patch.apply(old) #doctest: +ELLIPSIS {...} """ - def __init__(self, patch): + def __init__(self, patch, pointer_cls=JsonPointer): self.patch = patch + self.pointer_cls = pointer_cls self.operations = { 'remove': RemoveOperation, @@ -246,19 +250,22 @@ class JsonPatch(object): return not(self == other) @classmethod - def from_string(cls, patch_str): + def from_string(cls, patch_str, pointer_cls=JsonPointer): """Creates JsonPatch instance from string source. :param patch_str: JSON patch as raw string. - :type patch_str: str + :type pointer_cls: str + + :param pointer_cls: JSON pointer (sub)class. + :type pointer_cls: Type[JsonPointer] :return: :class:`JsonPatch` instance. """ patch = _jsonloads(patch_str) - return cls(patch) + return cls(patch, pointer_cls=pointer_cls) @classmethod - def from_diff(cls, src, dst, optimization=True): + def from_diff(cls, src, dst, optimization=True, pointer_cls=JsonPointer): """Creates JsonPatch instance based on comparison of two document objects. Json patch would be created for `src` argument against `dst` one. @@ -269,6 +276,9 @@ class JsonPatch(object): :param dst: Data source document object. :type dst: dict + :param pointer_cls: JSON pointer (sub)class. + :type pointer_cls: Type[JsonPointer] + :return: :class:`JsonPatch` instance. >>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]} @@ -279,10 +289,10 @@ class JsonPatch(object): True """ - builder = DiffBuilder() + builder = DiffBuilder(pointer_cls=pointer_cls) builder._compare_values('', None, src, dst) ops = list(builder.execute()) - return cls(ops) + return cls(ops, pointer_cls=pointer_cls) def to_string(self): """Returns patch set as JSON string.""" @@ -326,24 +336,25 @@ class JsonPatch(object): raise InvalidJsonPatch("Unknown operation {0!r}".format(op)) cls = self.operations[op] - return cls(operation) + return cls(operation, pointer_cls=self.pointer_cls) class PatchOperation(object): """A single operation inside a JSON Patch.""" - def __init__(self, operation): + def __init__(self, operation, pointer_cls=JsonPointer): + self.pointer_cls = pointer_cls if not operation.__contains__('path'): raise InvalidJsonPatch("Operation must have a 'path' member") - if isinstance(operation['path'], JsonPointer): + if isinstance(operation['path'], self.pointer_cls): self.location = operation['path'].path self.pointer = operation['path'] else: self.location = operation['path'] try: - self.pointer = JsonPointer(self.location) + self.pointer = self.pointer_cls(self.location) except TypeError as ex: raise InvalidJsonPatch("Invalid 'path'") @@ -511,10 +522,10 @@ class MoveOperation(PatchOperation): def apply(self, obj): try: - if isinstance(self.operation['from'], JsonPointer): + if isinstance(self.operation['from'], self.pointer_cls): from_ptr = self.operation['from'] else: - from_ptr = JsonPointer(self.operation['from']) + from_ptr = self.pointer_cls(self.operation['from']) except KeyError as ex: raise InvalidJsonPatch( "The operation does not contain a 'from' member") @@ -536,24 +547,24 @@ class MoveOperation(PatchOperation): obj = RemoveOperation({ 'op': 'remove', 'path': self.operation['from'] - }).apply(obj) + }, pointer_cls=self.pointer_cls).apply(obj) obj = AddOperation({ 'op': 'add', 'path': self.location, 'value': value - }).apply(obj) + }, pointer_cls=self.pointer_cls).apply(obj) return obj @property def from_path(self): - from_ptr = JsonPointer(self.operation['from']) + from_ptr = self.pointer_cls(self.operation['from']) return '/'.join(from_ptr.parts[:-1]) @property def from_key(self): - from_ptr = JsonPointer(self.operation['from']) + from_ptr = self.pointer_cls(self.operation['from']) try: return int(from_ptr.parts[-1]) except TypeError: @@ -561,7 +572,7 @@ class MoveOperation(PatchOperation): @from_key.setter def from_key(self, value): - from_ptr = JsonPointer(self.operation['from']) + from_ptr = self.pointer_cls(self.operation['from']) from_ptr.parts[-1] = str(value) self.operation['from'] = from_ptr.path @@ -624,7 +635,7 @@ class CopyOperation(PatchOperation): def apply(self, obj): try: - from_ptr = JsonPointer(self.operation['from']) + from_ptr = self.pointer_cls(self.operation['from']) except KeyError as ex: raise InvalidJsonPatch( "The operation does not contain a 'from' member") @@ -639,14 +650,15 @@ class CopyOperation(PatchOperation): 'op': 'add', 'path': self.location, 'value': value - }).apply(obj) + }, pointer_cls=self.pointer_cls).apply(obj) return obj class DiffBuilder(object): - def __init__(self): + def __init__(self, pointer_cls=JsonPointer): + self.pointer_cls = pointer_cls self.index_storage = [{}, {}] self.index_storage2 = [[], []] self.__root = root = [] @@ -715,7 +727,7 @@ class DiffBuilder(object): 'op': 'replace', 'path': op_second.location, 'value': op_second.operation['value'], - }).operation + }, pointer_cls=self.pointer_cls).operation curr = curr[1][1] continue @@ -736,14 +748,14 @@ class DiffBuilder(object): 'op': 'move', 'from': op.location, 'path': _path_join(path, key), - }) + }, pointer_cls=self.pointer_cls) self.insert(new_op) else: new_op = AddOperation({ 'op': 'add', 'path': _path_join(path, key), 'value': item, - }) + }, pointer_cls=self.pointer_cls) new_index = self.insert(new_op) self.store_index(item, new_index, _ST_ADD) @@ -751,7 +763,7 @@ class DiffBuilder(object): new_op = RemoveOperation({ 'op': 'remove', 'path': _path_join(path, key), - }) + }, pointer_cls=self.pointer_cls) index = self.take_index(item, _ST_ADD) new_index = self.insert(new_op) if index is not None: @@ -766,7 +778,7 @@ class DiffBuilder(object): 'op': 'move', 'from': new_op.location, 'path': op.location, - }) + }, pointer_cls=self.pointer_cls) new_index[2] = new_op else: @@ -780,7 +792,7 @@ class DiffBuilder(object): 'op': 'replace', 'path': _path_join(path, key), 'value': item, - })) + }, pointer_cls=self.pointer_cls)) def _compare_dicts(self, path, src, dst): src_keys = set(src.keys()) -- cgit v1.2.1