Skip to content

Commit ab775d1

Browse files
author
Artyom Nikitin
committed
feat: add custom json pointer support
1 parent 4fe5c2c commit ab775d1

File tree

1 file changed

+44
-32
lines changed

1 file changed

+44
-32
lines changed

jsonpatch.py

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def multidict(ordered_pairs):
106106
_jsonloads = functools.partial(json.loads, object_pairs_hook=multidict)
107107

108108

109-
def apply_patch(doc, patch, in_place=False):
109+
def apply_patch(doc, patch, in_place=False, pointer_cls=JsonPointer):
110110
"""Apply list of patches to specified json document.
111111
112112
:param doc: Document object.
@@ -137,13 +137,13 @@ def apply_patch(doc, patch, in_place=False):
137137
"""
138138

139139
if isinstance(patch, basestring):
140-
patch = JsonPatch.from_string(patch)
140+
patch = JsonPatch.from_string(patch, pointer_cls=pointer_cls)
141141
else:
142-
patch = JsonPatch(patch)
142+
patch = JsonPatch(patch, pointer_cls=pointer_cls)
143143
return patch.apply(doc, in_place)
144144

145145

146-
def make_patch(src, dst):
146+
def make_patch(src, dst, pointer_cls=JsonPointer):
147147
"""Generates patch by comparing two document objects. Actually is
148148
a proxy to :meth:`JsonPatch.from_diff` method.
149149
@@ -153,6 +153,9 @@ def make_patch(src, dst):
153153
:param dst: Data source document object.
154154
:type dst: dict
155155
156+
:param pointer_cls: JSON pointer (sub)class.
157+
:type pointer_cls: Type[JsonPointer]
158+
156159
>>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]}
157160
>>> dst = {'baz': 'qux', 'numbers': [1, 4, 7]}
158161
>>> patch = make_patch(src, dst)
@@ -161,7 +164,7 @@ def make_patch(src, dst):
161164
True
162165
"""
163166

164-
return JsonPatch.from_diff(src, dst)
167+
return JsonPatch.from_diff(src, dst, pointer_cls=pointer_cls)
165168

166169

167170
class JsonPatch(object):
@@ -210,8 +213,9 @@ class JsonPatch(object):
210213
... patch.apply(old) #doctest: +ELLIPSIS
211214
{...}
212215
"""
213-
def __init__(self, patch):
216+
def __init__(self, patch, pointer_cls=JsonPointer):
214217
self.patch = patch
218+
self.pointer_cls = pointer_cls
215219

216220
self.operations = {
217221
'remove': RemoveOperation,
@@ -246,19 +250,22 @@ def __ne__(self, other):
246250
return not(self == other)
247251

248252
@classmethod
249-
def from_string(cls, patch_str):
253+
def from_string(cls, patch_str, pointer_cls=JsonPointer):
250254
"""Creates JsonPatch instance from string source.
251255
252256
:param patch_str: JSON patch as raw string.
253-
:type patch_str: str
257+
:type pointer_cls: str
258+
259+
:param pointer_cls: JSON pointer (sub)class.
260+
:type pointer_cls: Type[JsonPointer]
254261
255262
:return: :class:`JsonPatch` instance.
256263
"""
257264
patch = _jsonloads(patch_str)
258-
return cls(patch)
265+
return cls(patch, pointer_cls=pointer_cls)
259266

260267
@classmethod
261-
def from_diff(cls, src, dst, optimization=True):
268+
def from_diff(cls, src, dst, optimization=True, pointer_cls=JsonPointer):
262269
"""Creates JsonPatch instance based on comparison of two document
263270
objects. Json patch would be created for `src` argument against `dst`
264271
one.
@@ -269,6 +276,9 @@ def from_diff(cls, src, dst, optimization=True):
269276
:param dst: Data source document object.
270277
:type dst: dict
271278
279+
:param pointer_cls: JSON pointer (sub)class.
280+
:type pointer_cls: Type[JsonPointer]
281+
272282
:return: :class:`JsonPatch` instance.
273283
274284
>>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]}
@@ -279,10 +289,10 @@ def from_diff(cls, src, dst, optimization=True):
279289
True
280290
"""
281291

282-
builder = DiffBuilder()
292+
builder = DiffBuilder(pointer_cls=pointer_cls)
283293
builder._compare_values('', None, src, dst)
284294
ops = list(builder.execute())
285-
return cls(ops)
295+
return cls(ops, pointer_cls=pointer_cls)
286296

287297
def to_string(self):
288298
"""Returns patch set as JSON string."""
@@ -326,24 +336,25 @@ def _get_operation(self, operation):
326336
raise InvalidJsonPatch("Unknown operation {0!r}".format(op))
327337

328338
cls = self.operations[op]
329-
return cls(operation)
339+
return cls(operation, pointer_cls=self.pointer_cls)
330340

331341

332342
class PatchOperation(object):
333343
"""A single operation inside a JSON Patch."""
334344

335-
def __init__(self, operation):
345+
def __init__(self, operation, pointer_cls=JsonPointer):
346+
self.pointer_cls = pointer_cls
336347

337348
if not operation.__contains__('path'):
338349
raise InvalidJsonPatch("Operation must have a 'path' member")
339350

340-
if isinstance(operation['path'], JsonPointer):
351+
if isinstance(operation['path'], self.pointer_cls):
341352
self.location = operation['path'].path
342353
self.pointer = operation['path']
343354
else:
344355
self.location = operation['path']
345356
try:
346-
self.pointer = JsonPointer(self.location)
357+
self.pointer = self.pointer_cls(self.location)
347358
except TypeError as ex:
348359
raise InvalidJsonPatch("Invalid 'path'")
349360

@@ -511,10 +522,10 @@ class MoveOperation(PatchOperation):
511522

512523
def apply(self, obj):
513524
try:
514-
if isinstance(self.operation['from'], JsonPointer):
525+
if isinstance(self.operation['from'], self.pointer_cls):
515526
from_ptr = self.operation['from']
516527
else:
517-
from_ptr = JsonPointer(self.operation['from'])
528+
from_ptr = self.pointer_cls(self.operation['from'])
518529
except KeyError as ex:
519530
raise InvalidJsonPatch(
520531
"The operation does not contain a 'from' member")
@@ -536,32 +547,32 @@ def apply(self, obj):
536547
obj = RemoveOperation({
537548
'op': 'remove',
538549
'path': self.operation['from']
539-
}).apply(obj)
550+
}, pointer_cls=self.pointer_cls).apply(obj)
540551

541552
obj = AddOperation({
542553
'op': 'add',
543554
'path': self.location,
544555
'value': value
545-
}).apply(obj)
556+
}, pointer_cls=self.pointer_cls).apply(obj)
546557

547558
return obj
548559

549560
@property
550561
def from_path(self):
551-
from_ptr = JsonPointer(self.operation['from'])
562+
from_ptr = self.pointer_cls(self.operation['from'])
552563
return '/'.join(from_ptr.parts[:-1])
553564

554565
@property
555566
def from_key(self):
556-
from_ptr = JsonPointer(self.operation['from'])
567+
from_ptr = self.pointer_cls(self.operation['from'])
557568
try:
558569
return int(from_ptr.parts[-1])
559570
except TypeError:
560571
return from_ptr.parts[-1]
561572

562573
@from_key.setter
563574
def from_key(self, value):
564-
from_ptr = JsonPointer(self.operation['from'])
575+
from_ptr = self.pointer_cls(self.operation['from'])
565576
from_ptr.parts[-1] = str(value)
566577
self.operation['from'] = from_ptr.path
567578

@@ -624,7 +635,7 @@ class CopyOperation(PatchOperation):
624635

625636
def apply(self, obj):
626637
try:
627-
from_ptr = JsonPointer(self.operation['from'])
638+
from_ptr = self.pointer_cls(self.operation['from'])
628639
except KeyError as ex:
629640
raise InvalidJsonPatch(
630641
"The operation does not contain a 'from' member")
@@ -639,14 +650,15 @@ def apply(self, obj):
639650
'op': 'add',
640651
'path': self.location,
641652
'value': value
642-
}).apply(obj)
653+
}, pointer_cls=self.pointer_cls).apply(obj)
643654

644655
return obj
645656

646657

647658
class DiffBuilder(object):
648659

649-
def __init__(self):
660+
def __init__(self, pointer_cls=JsonPointer):
661+
self.pointer_cls = pointer_cls
650662
self.index_storage = [{}, {}]
651663
self.index_storage2 = [[], []]
652664
self.__root = root = []
@@ -715,7 +727,7 @@ def execute(self):
715727
'op': 'replace',
716728
'path': op_second.location,
717729
'value': op_second.operation['value'],
718-
}).operation
730+
}, pointer_cls=self.pointer_cls).operation
719731
curr = curr[1][1]
720732
continue
721733

@@ -736,22 +748,22 @@ def _item_added(self, path, key, item):
736748
'op': 'move',
737749
'from': op.location,
738750
'path': _path_join(path, key),
739-
})
751+
}, pointer_cls=self.pointer_cls)
740752
self.insert(new_op)
741753
else:
742754
new_op = AddOperation({
743755
'op': 'add',
744756
'path': _path_join(path, key),
745757
'value': item,
746-
})
758+
}, pointer_cls=self.pointer_cls)
747759
new_index = self.insert(new_op)
748760
self.store_index(item, new_index, _ST_ADD)
749761

750762
def _item_removed(self, path, key, item):
751763
new_op = RemoveOperation({
752764
'op': 'remove',
753765
'path': _path_join(path, key),
754-
})
766+
}, pointer_cls=self.pointer_cls)
755767
index = self.take_index(item, _ST_ADD)
756768
new_index = self.insert(new_op)
757769
if index is not None:
@@ -766,7 +778,7 @@ def _item_removed(self, path, key, item):
766778
'op': 'move',
767779
'from': new_op.location,
768780
'path': op.location,
769-
})
781+
}, pointer_cls=self.pointer_cls)
770782
new_index[2] = new_op
771783

772784
else:
@@ -780,7 +792,7 @@ def _item_replaced(self, path, key, item):
780792
'op': 'replace',
781793
'path': _path_join(path, key),
782794
'value': item,
783-
}))
795+
}, pointer_cls=self.pointer_cls))
784796

785797
def _compare_dicts(self, path, src, dst):
786798
src_keys = set(src.keys())

0 commit comments

Comments
 (0)