Skip to content

Commit 3a95635

Browse files
authored
Merge pull request #114 from tzoiker/feature/custom-pointer
Allow custom JSON pointer class
2 parents 24b5e86 + eca4f8a commit 3a95635

File tree

2 files changed

+180
-31
lines changed

2 files changed

+180
-31
lines changed

jsonpatch.py

Lines changed: 50 additions & 31 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.
@@ -119,6 +119,9 @@ def apply_patch(doc, patch, in_place=False):
119119
By default patch will be applied to document copy.
120120
:type in_place: bool
121121
122+
:param pointer_cls: JSON pointer class to use.
123+
:type pointer_cls: Type[JsonPointer]
124+
122125
:return: Patched document object.
123126
:rtype: dict
124127
@@ -137,13 +140,13 @@ def apply_patch(doc, patch, in_place=False):
137140
"""
138141

139142
if isinstance(patch, basestring):
140-
patch = JsonPatch.from_string(patch)
143+
patch = JsonPatch.from_string(patch, pointer_cls=pointer_cls)
141144
else:
142-
patch = JsonPatch(patch)
145+
patch = JsonPatch(patch, pointer_cls=pointer_cls)
143146
return patch.apply(doc, in_place)
144147

145148

146-
def make_patch(src, dst):
149+
def make_patch(src, dst, pointer_cls=JsonPointer):
147150
"""Generates patch by comparing two document objects. Actually is
148151
a proxy to :meth:`JsonPatch.from_diff` method.
149152
@@ -153,6 +156,9 @@ def make_patch(src, dst):
153156
:param dst: Data source document object.
154157
:type dst: dict
155158
159+
:param pointer_cls: JSON pointer class to use.
160+
:type pointer_cls: Type[JsonPointer]
161+
156162
>>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]}
157163
>>> dst = {'baz': 'qux', 'numbers': [1, 4, 7]}
158164
>>> patch = make_patch(src, dst)
@@ -161,7 +167,7 @@ def make_patch(src, dst):
161167
True
162168
"""
163169

164-
return JsonPatch.from_diff(src, dst)
170+
return JsonPatch.from_diff(src, dst, pointer_cls=pointer_cls)
165171

166172

167173
class JsonPatch(object):
@@ -213,8 +219,9 @@ class JsonPatch(object):
213219
... patch.apply(old) #doctest: +ELLIPSIS
214220
{...}
215221
"""
216-
def __init__(self, patch):
222+
def __init__(self, patch, pointer_cls=JsonPointer):
217223
self.patch = patch
224+
self.pointer_cls = pointer_cls
218225

219226
self.operations = {
220227
'remove': RemoveOperation,
@@ -256,23 +263,30 @@ def __ne__(self, other):
256263
return not(self == other)
257264

258265
@classmethod
259-
def from_string(cls, patch_str, loads=None):
266+
def from_string(cls, patch_str, loads=None, pointer_cls=JsonPointer):
260267
"""Creates JsonPatch instance from string source.
261268
262269
:param patch_str: JSON patch as raw string.
263270
:type patch_str: str
271+
264272
:param loads: A function of one argument that loads a serialized
265273
JSON string.
266274
:type loads: function
267275
276+
:param pointer_cls: JSON pointer class to use.
277+
:type pointer_cls: Type[JsonPointer]
278+
268279
:return: :class:`JsonPatch` instance.
269280
"""
270281
json_loader = loads or cls.json_loader
271282
patch = json_loader(patch_str)
272-
return cls(patch)
283+
return cls(patch, pointer_cls=pointer_cls)
273284

274285
@classmethod
275-
def from_diff(cls, src, dst, optimization=True, dumps=None):
286+
def from_diff(
287+
cls, src, dst, optimization=True, dumps=None,
288+
pointer_cls=JsonPointer,
289+
):
276290
"""Creates JsonPatch instance based on comparison of two document
277291
objects. Json patch would be created for `src` argument against `dst`
278292
one.
@@ -287,6 +301,9 @@ def from_diff(cls, src, dst, optimization=True, dumps=None):
287301
JSON string.
288302
:type dumps: function
289303
304+
:param pointer_cls: JSON pointer class to use.
305+
:type pointer_cls: Type[JsonPointer]
306+
290307
:return: :class:`JsonPatch` instance.
291308
292309
>>> src = {'foo': 'bar', 'numbers': [1, 3, 4, 8]}
@@ -297,10 +314,10 @@ def from_diff(cls, src, dst, optimization=True, dumps=None):
297314
True
298315
"""
299316
json_dumper = dumps or cls.json_dumper
300-
builder = DiffBuilder(json_dumper)
317+
builder = DiffBuilder(json_dumper, pointer_cls=pointer_cls)
301318
builder._compare_values('', None, src, dst)
302319
ops = list(builder.execute())
303-
return cls(ops)
320+
return cls(ops, pointer_cls=pointer_cls)
304321

305322
def to_string(self, dumps=None):
306323
"""Returns patch set as JSON string."""
@@ -345,24 +362,25 @@ def _get_operation(self, operation):
345362
raise InvalidJsonPatch("Unknown operation {0!r}".format(op))
346363

347364
cls = self.operations[op]
348-
return cls(operation)
365+
return cls(operation, pointer_cls=self.pointer_cls)
349366

350367

351368
class PatchOperation(object):
352369
"""A single operation inside a JSON Patch."""
353370

354-
def __init__(self, operation):
371+
def __init__(self, operation, pointer_cls=JsonPointer):
372+
self.pointer_cls = pointer_cls
355373

356374
if not operation.__contains__('path'):
357375
raise InvalidJsonPatch("Operation must have a 'path' member")
358376

359-
if isinstance(operation['path'], JsonPointer):
377+
if isinstance(operation['path'], self.pointer_cls):
360378
self.location = operation['path'].path
361379
self.pointer = operation['path']
362380
else:
363381
self.location = operation['path']
364382
try:
365-
self.pointer = JsonPointer(self.location)
383+
self.pointer = self.pointer_cls(self.location)
366384
except TypeError as ex:
367385
raise InvalidJsonPatch("Invalid 'path'")
368386

@@ -530,10 +548,10 @@ class MoveOperation(PatchOperation):
530548

531549
def apply(self, obj):
532550
try:
533-
if isinstance(self.operation['from'], JsonPointer):
551+
if isinstance(self.operation['from'], self.pointer_cls):
534552
from_ptr = self.operation['from']
535553
else:
536-
from_ptr = JsonPointer(self.operation['from'])
554+
from_ptr = self.pointer_cls(self.operation['from'])
537555
except KeyError as ex:
538556
raise InvalidJsonPatch(
539557
"The operation does not contain a 'from' member")
@@ -555,32 +573,32 @@ def apply(self, obj):
555573
obj = RemoveOperation({
556574
'op': 'remove',
557575
'path': self.operation['from']
558-
}).apply(obj)
576+
}, pointer_cls=self.pointer_cls).apply(obj)
559577

560578
obj = AddOperation({
561579
'op': 'add',
562580
'path': self.location,
563581
'value': value
564-
}).apply(obj)
582+
}, pointer_cls=self.pointer_cls).apply(obj)
565583

566584
return obj
567585

568586
@property
569587
def from_path(self):
570-
from_ptr = JsonPointer(self.operation['from'])
588+
from_ptr = self.pointer_cls(self.operation['from'])
571589
return '/'.join(from_ptr.parts[:-1])
572590

573591
@property
574592
def from_key(self):
575-
from_ptr = JsonPointer(self.operation['from'])
593+
from_ptr = self.pointer_cls(self.operation['from'])
576594
try:
577595
return int(from_ptr.parts[-1])
578596
except TypeError:
579597
return from_ptr.parts[-1]
580598

581599
@from_key.setter
582600
def from_key(self, value):
583-
from_ptr = JsonPointer(self.operation['from'])
601+
from_ptr = self.pointer_cls(self.operation['from'])
584602
from_ptr.parts[-1] = str(value)
585603
self.operation['from'] = from_ptr.path
586604

@@ -643,7 +661,7 @@ class CopyOperation(PatchOperation):
643661

644662
def apply(self, obj):
645663
try:
646-
from_ptr = JsonPointer(self.operation['from'])
664+
from_ptr = self.pointer_cls(self.operation['from'])
647665
except KeyError as ex:
648666
raise InvalidJsonPatch(
649667
"The operation does not contain a 'from' member")
@@ -658,15 +676,16 @@ def apply(self, obj):
658676
'op': 'add',
659677
'path': self.location,
660678
'value': value
661-
}).apply(obj)
679+
}, pointer_cls=self.pointer_cls).apply(obj)
662680

663681
return obj
664682

665683

666684
class DiffBuilder(object):
667685

668-
def __init__(self, dumps=json.dumps):
686+
def __init__(self, dumps=json.dumps, pointer_cls=JsonPointer):
669687
self.dumps = dumps
688+
self.pointer_cls = pointer_cls
670689
self.index_storage = [{}, {}]
671690
self.index_storage2 = [[], []]
672691
self.__root = root = []
@@ -735,7 +754,7 @@ def execute(self):
735754
'op': 'replace',
736755
'path': op_second.location,
737756
'value': op_second.operation['value'],
738-
}).operation
757+
}, pointer_cls=self.pointer_cls).operation
739758
curr = curr[1][1]
740759
continue
741760

@@ -756,22 +775,22 @@ def _item_added(self, path, key, item):
756775
'op': 'move',
757776
'from': op.location,
758777
'path': _path_join(path, key),
759-
})
778+
}, pointer_cls=self.pointer_cls)
760779
self.insert(new_op)
761780
else:
762781
new_op = AddOperation({
763782
'op': 'add',
764783
'path': _path_join(path, key),
765784
'value': item,
766-
})
785+
}, pointer_cls=self.pointer_cls)
767786
new_index = self.insert(new_op)
768787
self.store_index(item, new_index, _ST_ADD)
769788

770789
def _item_removed(self, path, key, item):
771790
new_op = RemoveOperation({
772791
'op': 'remove',
773792
'path': _path_join(path, key),
774-
})
793+
}, pointer_cls=self.pointer_cls)
775794
index = self.take_index(item, _ST_ADD)
776795
new_index = self.insert(new_op)
777796
if index is not None:
@@ -786,7 +805,7 @@ def _item_removed(self, path, key, item):
786805
'op': 'move',
787806
'from': new_op.location,
788807
'path': op.location,
789-
})
808+
}, pointer_cls=self.pointer_cls)
790809
new_index[2] = new_op
791810

792811
else:
@@ -800,7 +819,7 @@ def _item_replaced(self, path, key, item):
800819
'op': 'replace',
801820
'path': _path_join(path, key),
802821
'value': item,
803-
}))
822+
}, pointer_cls=self.pointer_cls))
804823

805824
def _compare_dicts(self, path, src, dst):
806825
src_keys = set(src.keys())

0 commit comments

Comments
 (0)