11
11
# are met:
12
12
#
13
13
# 1. Redistributions of source code must retain the above copyright
14
- # notice, this list of conditions and the following disclaimer.
14
+ # notice, this list of conditions and the following disclaimer.
15
15
# 2. Redistributions in binary form must reproduce the above copyright
16
- # notice, this list of conditions and the following disclaimer in the
17
- # documentation and/or other materials provided with the distribution.
16
+ # notice, this list of conditions and the following disclaimer in the
17
+ # documentation and/or other materials provided with the distribution.
18
18
# 3. The name of the author may not be used to endorse or promote products
19
- # derived from this software without specific prior written permission.
19
+ # derived from this software without specific prior written permission.
20
20
#
21
21
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22
22
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
30
30
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
31
#
32
32
33
- from __future__ import unicode_literals
34
-
35
33
""" Identify specific nodes in a JSON document (RFC 6901) """
36
34
37
- try :
38
- from collections .abc import Mapping , Sequence
39
- except ImportError :
40
- from collections import Mapping , Sequence
35
+ from __future__ import unicode_literals
41
36
42
37
# Will be parsed by setup.py to determine package metadata
43
38
__author__ = 'Stefan Kögl <[email protected] >'
50
45
from urllib import unquote
51
46
from itertools import izip
52
47
str = unicode
53
- except ImportError : # Python 3
48
+ except ImportError : # Python 3
54
49
from urllib .parse import unquote
55
50
izip = zip
56
51
52
+ try :
53
+ from collections .abc import Mapping , Sequence
54
+ except ImportError : # Python 3
55
+ from collections import Mapping , Sequence
56
+
57
57
from itertools import tee
58
58
import re
59
59
import copy
60
60
61
61
62
- # array indices must not contain leading zeros, signs, spaces, decimals, etc
63
- RE_ARRAY_INDEX = re .compile ('0|[1-9][0-9]*$' )
64
-
65
-
66
- class JsonPointerException (Exception ):
67
- pass
62
+ _nothing = object ()
68
63
69
64
70
- class EndOfList ( object ):
71
- """ Result of accessing element "-" of a list """
65
+ def set_pointer ( doc , pointer , value , inplace = True ):
66
+ """Resolves pointer against doc and sets the value of the target within doc.
72
67
73
- def __init__ ( self , list_ ):
74
- self . list_ = list_
68
+ With inplace set to true, doc is modified as long as pointer is not the
69
+ root.
75
70
71
+ >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}}
76
72
77
- def __repr__ ( self ):
78
- return '{cls}({lst})' . format ( cls = self . __class__ . __name__ ,
79
- lst = repr ( self . list_ ))
73
+ >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
74
+ {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
75
+ True
80
76
77
+ >>> set_pointer(obj, '/foo/yet%20another%20prop', 'added prop') == \
78
+ {'foo': {'another prop': {'baz': 'A string'}, 'yet another prop': 'added prop', 'anArray': [{'prop': 55}]}}
79
+ True
80
+ """
81
81
82
- _nothing = object ()
82
+ pointer = JsonPointer (pointer )
83
+ return pointer .set (doc , value , inplace )
83
84
84
85
85
86
def resolve_pointer (doc , pointer , default = _nothing ):
86
- """
87
- Resolves pointer against doc and returns the referenced object
87
+ """ Resolves pointer against doc and returns the referenced object
88
88
89
- >>> obj = {" foo" : {" anArray" : [ {" prop" : 44}], " another prop" : {" baz": " A string" }}}
89
+ >>> obj = {' foo' : {' anArray' : [ {' prop' : 44}], ' another prop' : {' baz': ' A string' }}}
90
90
91
91
>>> resolve_pointer(obj, '') == obj
92
92
True
@@ -105,37 +105,53 @@ def resolve_pointer(doc, pointer, default=_nothing):
105
105
106
106
>>> resolve_pointer(obj, '/some/path', None) == None
107
107
True
108
-
109
108
"""
110
109
111
110
pointer = JsonPointer (pointer )
112
111
return pointer .resolve (doc , default )
113
112
114
- def set_pointer (doc , pointer , value , inplace = True ):
115
- """
116
- Resolves pointer against doc and sets the value of the target within doc.
117
113
118
- With inplace set to true, doc is modified as long as pointer is not the
119
- root.
114
+ def pairwise ( iterable ):
115
+ """ Transforms a list to a list of tuples of adjacent items
120
116
121
- >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
117
+ s -> (s0,s1), (s1,s2), (s2, s3), ...
122
118
123
- >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
124
- {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
125
- True
119
+ >>> list(pairwise([]))
120
+ []
126
121
127
- >>> set_pointer(obj, '/foo/yet%20another%20prop', 'added prop') == \
128
- {'foo': {'another prop': {'baz': 'A string'}, 'yet another prop': 'added prop', 'anArray': [{'prop': 55}]}}
129
- True
122
+ >>> list(pairwise([1]))
123
+ []
130
124
125
+ >>> list(pairwise([1, 2, 3, 4]))
126
+ [(1, 2), (2, 3), (3, 4)]
131
127
"""
128
+ a , b = tee (iterable )
129
+ for _ in b :
130
+ break
131
+ return izip (a , b )
132
132
133
- pointer = JsonPointer (pointer )
134
- return pointer .set (doc , value , inplace )
133
+
134
+ class JsonPointerException (Exception ):
135
+ pass
136
+
137
+
138
+ class EndOfList (object ):
139
+ """Result of accessing element "-" of a list"""
140
+
141
+ def __init__ (self , list_ ):
142
+ self .list_ = list_
143
+
144
+ def __repr__ (self ):
145
+ return '{cls}({lst})' .format (cls = self .__class__ .__name__ ,
146
+ lst = repr (self .list_ ))
135
147
136
148
137
149
class JsonPointer (object ):
138
- """ A JSON Pointer that can reference parts of an JSON document """
150
+ """A JSON Pointer that can reference parts of an JSON document"""
151
+
152
+ # Array indices must not contain:
153
+ # leading zeros, signs, spaces, decimals, etc
154
+ _RE_ARRAY_INDEX = re .compile ('0|[1-9][0-9]*$' )
139
155
140
156
def __init__ (self , pointer ):
141
157
parts = pointer .split ('/' )
@@ -146,9 +162,8 @@ def __init__(self, pointer):
146
162
parts = [unescape (part ) for part in parts ]
147
163
self .parts = parts
148
164
149
-
150
165
def to_last (self , doc ):
151
- """ Resolves ptr until the last step, returns (sub-doc, last-step) """
166
+ """Resolves ptr until the last step, returns (sub-doc, last-step)"""
152
167
153
168
if not self .parts :
154
169
return doc , None
@@ -158,7 +173,6 @@ def to_last(self, doc):
158
173
159
174
return doc , self .get_part (doc , self .parts [- 1 ])
160
175
161
-
162
176
def resolve (self , doc , default = _nothing ):
163
177
"""Resolves the pointer against doc and returns the referenced object"""
164
178
@@ -174,11 +188,10 @@ def resolve(self, doc, default=_nothing):
174
188
175
189
return doc
176
190
177
-
178
191
get = resolve
179
192
180
193
def set (self , doc , value , inplace = True ):
181
- """ Resolve the pointer against the doc and replace the target with value. """
194
+ """Resolve the pointer against the doc and replace the target with value."""
182
195
183
196
if len (self .parts ) == 0 :
184
197
if inplace :
@@ -194,7 +207,7 @@ def set(self, doc, value, inplace=True):
194
207
return doc
195
208
196
209
def get_part (self , doc , part ):
197
- """ Returns the next step in the correct type """
210
+ """Returns the next step in the correct type"""
198
211
199
212
if isinstance (doc , Mapping ):
200
213
return part
@@ -204,26 +217,27 @@ def get_part(self, doc, part):
204
217
if part == '-' :
205
218
return part
206
219
207
- if not RE_ARRAY_INDEX .match (str (part )):
208
- raise JsonPointerException ("'%s' is not a valid list index" % ( part , ) )
220
+ if not self . _RE_ARRAY_INDEX .match (str (part )):
221
+ raise JsonPointerException ("'%s' is not a valid sequence index" % part )
209
222
210
223
return int (part )
211
224
212
225
elif hasattr (doc , '__getitem__' ):
213
- # Allow indexing via ducktyping if the target has defined __getitem__
226
+ # Allow indexing via ducktyping
227
+ # if the target has defined __getitem__
214
228
return part
215
229
216
230
else :
217
- raise JsonPointerException ("Document '%s' does not support indexing, "
218
- "must be dict/list or support __getitem__" % type (doc ))
219
-
231
+ raise JsonPointerException ("document '%s' does not support indexing, "
232
+ "must be mapping/sequence or support __getitem__" % type (doc ))
220
233
221
234
def walk (self , doc , part ):
222
- """ Walks one step in doc and returns the referenced part """
235
+ """Walks one step in doc and returns the referenced part"""
223
236
224
237
part = self .get_part (doc , part )
225
238
226
- assert (type (doc ) in (dict , list ) or hasattr (doc , '__getitem__' )), "invalid document type %s" % (type (doc ),)
239
+ assert hasattr (doc , '__getitem__' ), \
240
+ 'invalid document type %s' % type (doc )
227
241
228
242
if isinstance (doc , Mapping ):
229
243
try :
@@ -257,32 +271,32 @@ def __contains__(self, item):
257
271
258
272
@property
259
273
def path (self ):
260
- """ Returns the string representation of the pointer
274
+ """Returns the string representation of the pointer
261
275
262
276
>>> ptr = JsonPointer('/~0/0/~1').path == '/~0/0/~1'
263
277
"""
264
278
parts = [escape (part ) for part in self .parts ]
265
279
return '' .join ('/' + part for part in parts )
266
280
267
281
def __eq__ (self , other ):
268
- """ compares a pointer to another object
282
+ """Compares a pointer to another object
269
283
270
284
Pointers can be compared by comparing their strings (or splitted
271
285
strings), because no two different parts can point to the same
272
- structure in an object (eg no different number representations) """
286
+ structure in an object (eg no different number representations)
287
+ """
273
288
274
289
if not isinstance (other , JsonPointer ):
275
290
return False
276
291
277
292
return self .parts == other .parts
278
293
279
-
280
294
def __hash__ (self ):
281
295
return hash (tuple (self .parts ))
282
296
283
297
@classmethod
284
298
def from_parts (cls , parts ):
285
- """ Constructs a JsonPointer from a list of (unescaped) paths
299
+ """Constructs a JsonPointer from a list of (unescaped) paths
286
300
287
301
>>> JsonPointer.from_parts(['a', '~', '/', 0]).path == '/a/~0/~1/0'
288
302
True
@@ -292,25 +306,6 @@ def from_parts(cls, parts):
292
306
return ptr
293
307
294
308
295
-
296
- def pairwise (iterable ):
297
- """ s -> (s0,s1), (s1,s2), (s2, s3), ...
298
-
299
- >>> list(pairwise([]))
300
- []
301
-
302
- >>> list(pairwise([1]))
303
- []
304
-
305
- >>> list(pairwise([1, 2, 3, 4]))
306
- [(1, 2), (2, 3), (3, 4)]
307
- """
308
- a , b = tee (iterable )
309
- for _ in b :
310
- break
311
- return izip (a , b )
312
-
313
-
314
309
def escape (s ):
315
310
return s .replace ('~' , '~0' ).replace ('/' , '~1' )
316
311
0 commit comments