30
30
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
31
#
32
32
33
+ """ Apply JSON-Patches (RFC 6902) """
34
+
33
35
from __future__ import unicode_literals
34
36
35
- """ Apply JSON-Patches (RFC 6902) """
37
+ import collections
38
+ import copy
39
+ import functools
40
+ import inspect
41
+ import json
42
+ import operator
43
+ import sys
44
+
45
+ from jsonpointer import JsonPointer , JsonPointerException
36
46
37
47
# Will be parsed by setup.py to determine package metadata
38
48
__author__ = 'Stefan Kögl <[email protected] >'
39
49
__version__ = '1.3'
40
50
__website__ = 'https://github.com/stefankoegl/python-json-patch'
41
51
__license__ = 'Modified BSD License'
42
52
43
- import copy
44
- import sys
45
- import operator
46
- import collections
47
-
48
- import json
49
-
50
- import jsonpointer
51
-
52
53
if sys .version_info >= (3 , 0 ):
53
- basestring = (bytes , str )
54
+ basestring = (bytes , str ) # pylint: disable=C0103
54
55
55
56
56
- JsonPointerException = jsonpointer .JsonPointerException
57
-
58
57
class JsonPatchException (Exception ):
59
58
"""Base Json Patch exception"""
60
59
@@ -67,22 +66,23 @@ class JsonPatchConflict(JsonPatchException):
67
66
- etc.
68
67
"""
69
68
69
+
70
70
class JsonPatchTestFailed (JsonPatchException , AssertionError ):
71
71
""" A Test operation failed """
72
72
73
73
74
74
def multidict (ordered_pairs ):
75
75
"""Convert duplicate keys values to lists."""
76
76
# read all values into lists
77
- d = collections .defaultdict (list )
78
- for k , v in ordered_pairs :
79
- d [ k ].append (v )
77
+ mdict = collections .defaultdict (list )
78
+ for key , value in ordered_pairs :
79
+ mdict [ key ].append (value )
80
80
81
81
# unpack lists that have only 1 item
82
- for k , v in d .items ():
83
- if len (v ) == 1 :
84
- d [ k ] = v [0 ]
85
- return dict (d )
82
+ for key , values in mdict .items ():
83
+ if len (values ) == 1 :
84
+ mdict [ key ] = values [0 ]
85
+ return dict (mdict )
86
86
87
87
88
88
def get_loadjson ():
@@ -94,9 +94,6 @@ def get_loadjson():
94
94
function with object_pairs_hook set to multidict for Python versions that
95
95
support the parameter. """
96
96
97
- import inspect
98
- import functools
99
-
100
97
argspec = inspect .getargspec (json .load )
101
98
if 'object_pairs_hook' not in argspec .args :
102
99
return json .load
@@ -123,12 +120,14 @@ def apply_patch(doc, patch, in_place=False):
123
120
:rtype: dict
124
121
125
122
>>> doc = {'foo': 'bar'}
126
- >>> other = apply_patch(doc, [{'op': 'add', 'path': '/baz', 'value': 'qux'}])
123
+ >>> patch = [{'op': 'add', 'path': '/baz', 'value': 'qux'}]
124
+ >>> other = apply_patch(doc, patch)
127
125
>>> doc is not other
128
126
True
129
127
>>> other == {'foo': 'bar', 'baz': 'qux'}
130
128
True
131
- >>> apply_patch(doc, [{'op': 'add', 'path': '/baz', 'value': 'qux'}], in_place=True) == {'foo': 'bar', 'baz': 'qux'}
129
+ >>> patch = [{'op': 'add', 'path': '/baz', 'value': 'qux'}]
130
+ >>> apply_patch(doc, patch, in_place=True) == {'foo': 'bar', 'baz': 'qux'}
132
131
True
133
132
>>> doc == other
134
133
True
@@ -140,6 +139,7 @@ def apply_patch(doc, patch, in_place=False):
140
139
patch = JsonPatch (patch )
141
140
return patch .apply (doc , in_place )
142
141
142
+
143
143
def make_patch (src , dst ):
144
144
"""Generates patch by comparing of two document objects. Actually is
145
145
a proxy to :meth:`JsonPatch.from_diff` method.
@@ -230,19 +230,16 @@ def __bool__(self):
230
230
def __iter__ (self ):
231
231
return iter (self .patch )
232
232
233
-
234
233
def __hash__ (self ):
235
234
return hash (tuple (self ._ops ))
236
235
237
-
238
236
def __eq__ (self , other ):
239
237
if not isinstance (other , JsonPatch ):
240
238
return False
241
239
242
240
return len (list (self ._ops )) == len (list (other ._ops )) and \
243
241
all (map (operator .eq , self ._ops , other ._ops ))
244
242
245
-
246
243
@classmethod
247
244
def from_string (cls , patch_str ):
248
245
"""Creates JsonPatch instance from string source.
@@ -298,7 +295,9 @@ def compare_dict(path, src, dst):
298
295
yield operation
299
296
for key in dst :
300
297
if key not in src :
301
- yield {'op' : 'add' , 'path' : '/' .join (path + [key ]), 'value' : dst [key ]}
298
+ yield {'op' : 'add' ,
299
+ 'path' : '/' .join (path + [key ]),
300
+ 'value' : dst [key ]}
302
301
303
302
def compare_list (path , src , dst ):
304
303
lsrc , ldst = len (src ), len (dst )
@@ -309,7 +308,9 @@ def compare_list(path, src, dst):
309
308
if lsrc < ldst :
310
309
for idx in range (lsrc , ldst ):
311
310
current = path + [str (idx )]
312
- yield {'op' : 'add' , 'path' : '/' .join (current ), 'value' : dst [idx ]}
311
+ yield {'op' : 'add' ,
312
+ 'path' : '/' .join (current ),
313
+ 'value' : dst [idx ]}
313
314
elif lsrc > ldst :
314
315
for idx in reversed (range (ldst , lsrc )):
315
316
yield {'op' : 'remove' , 'path' : '/' .join (path + [str (idx )])}
@@ -361,28 +362,24 @@ def _get_operation(self, operation):
361
362
return cls (operation )
362
363
363
364
364
-
365
365
class PatchOperation (object ):
366
366
"""A single operation inside a JSON Patch."""
367
367
368
368
def __init__ (self , operation ):
369
369
self .location = operation ['path' ]
370
- self .pointer = jsonpointer . JsonPointer (self .location )
370
+ self .pointer = JsonPointer (self .location )
371
371
self .operation = operation
372
372
373
373
def apply (self , obj ):
374
374
"""Abstract method that applies patch operation to specified object."""
375
375
raise NotImplementedError ('should implement patch operation.' )
376
376
377
-
378
377
def __hash__ (self ):
379
378
return hash (frozenset (self .operation .items ()))
380
379
381
-
382
380
def __eq__ (self , other ):
383
381
if not isinstance (other , PatchOperation ):
384
382
return False
385
-
386
383
return self .operation == other .operation
387
384
388
385
@@ -409,24 +406,22 @@ def apply(self, obj):
409
406
# type is already checked in to_last(), so we assert here
410
407
# for consistency
411
408
assert isinstance (subobj , list ) or isinstance (subobj , dict ), \
412
- "invalid document type %s" ( type (doc ), )
409
+ "invalid document type %s" % type (subobj )
413
410
414
411
if isinstance (subobj , list ):
415
412
416
413
if part == '-' :
417
- subobj .append (value )
414
+ subobj .append (value ) # pylint: disable=E1103
418
415
419
416
elif part > len (subobj ) or part < 0 :
420
417
raise JsonPatchConflict ("can't insert outside of list" )
421
418
422
419
else :
423
- subobj .insert (part , value )
420
+ subobj .insert (part , value ) # pylint: disable=E1103
424
421
425
422
elif isinstance (subobj , dict ):
426
423
if part is None :
427
- # we're replacing the root
428
- obj = value
429
-
424
+ obj = value # we're replacing the root
430
425
else :
431
426
subobj [part ] = value
432
427
@@ -443,7 +438,7 @@ def apply(self, obj):
443
438
# type is already checked in to_last(), so we assert here
444
439
# for consistency
445
440
assert isinstance (subobj , list ) or isinstance (subobj , dict ), \
446
- "invalid document type %s" ( type ( doc ),)
441
+ "invalid document type %s" % subobj
447
442
448
443
if part is None :
449
444
return value
@@ -454,8 +449,8 @@ def apply(self, obj):
454
449
455
450
elif isinstance (subobj , dict ):
456
451
if not part in subobj :
457
- raise JsonPatchConflict ("can't replace non-existant object '%s'"
458
- "" % part )
452
+ raise JsonPatchConflict ("can't replace non-existent object '%s'"
453
+ % part )
459
454
460
455
subobj [part ] = value
461
456
return obj
@@ -465,15 +460,24 @@ class MoveOperation(PatchOperation):
465
460
"""Moves an object property or an array element to new location."""
466
461
467
462
def apply (self , obj ):
468
- from_ptr = jsonpointer . JsonPointer (self .operation ['from' ])
463
+ from_ptr = JsonPointer (self .operation ['from' ])
469
464
subobj , part = from_ptr .to_last (obj )
470
465
value = subobj [part ]
471
466
472
467
if self .pointer .contains (from_ptr ):
473
468
raise JsonPatchException ('Cannot move values into its own children' )
474
469
475
- obj = RemoveOperation ({'op' : 'remove' , 'path' : self .operation ['from' ]}).apply (obj )
476
- obj = AddOperation ({'op' : 'add' , 'path' : self .location , 'value' : value }).apply (obj )
470
+ obj = RemoveOperation ({
471
+ 'op' : 'remove' ,
472
+ 'path' : self .operation ['from' ]
473
+ }).apply (obj )
474
+
475
+ obj = AddOperation ({
476
+ 'op' : 'add' ,
477
+ 'path' : self .location ,
478
+ 'value' : value
479
+ }).apply (obj )
480
+
477
481
return obj
478
482
479
483
@@ -487,14 +491,15 @@ def apply(self, obj):
487
491
val = subobj
488
492
else :
489
493
val = self .pointer .walk (subobj , part )
490
-
491
494
except JsonPointerException as ex :
492
495
raise JsonPatchTestFailed (str (ex ))
493
496
494
497
if 'value' in self .operation :
495
498
value = self .operation ['value' ]
496
499
if val != value :
497
- raise JsonPatchTestFailed ('%s is not equal to tested value %s (types %s and %s)' % (val , value , type (val ), type (value )))
500
+ raise JsonPatchTestFailed (
501
+ '%s is not equal to tested value %s (types %s and %s)'
502
+ % (val , value , type (val ), type (value )))
498
503
499
504
return obj
500
505
@@ -503,8 +508,14 @@ class CopyOperation(PatchOperation):
503
508
""" Copies an object property or an array element to a new location """
504
509
505
510
def apply (self , obj ):
506
- from_ptr = jsonpointer . JsonPointer (self .operation ['from' ])
511
+ from_ptr = JsonPointer (self .operation ['from' ])
507
512
subobj , part = from_ptr .to_last (obj )
508
513
value = copy .deepcopy (subobj [part ])
509
- obj = AddOperation ({'op' : 'add' , 'path' : self .location , 'value' : value }).apply (obj )
514
+
515
+ obj = AddOperation ({
516
+ 'op' : 'add' ,
517
+ 'path' : self .location ,
518
+ 'value' : value
519
+ }).apply (obj )
520
+
510
521
return obj
0 commit comments