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] >'
44
- __version__ = '1.5 '
39
+ __version__ = '1.4 '
45
40
__website__ = 'https://github.com/stefankoegl/python-json-pointer'
46
41
__license__ = 'Modified BSD License'
47
42
48
43
49
44
try :
50
45
from urllib import unquote
51
46
from itertools import izip
52
- except ImportError : # Python 3
47
+ except ImportError : # Python 3
53
48
from urllib .parse import unquote
54
49
izip = zip
55
50
51
+ try :
52
+ from collections .abc import Mapping , Sequence
53
+ except ImportError : # Python 3
54
+ from collections import Mapping , Sequence
55
+
56
56
from itertools import tee
57
57
import re
58
58
import copy
59
59
60
60
61
- # array indices must not contain leading zeros, signs, spaces, decimals, etc
62
- RE_ARRAY_INDEX = re .compile ('0|[1-9][0-9]*$' )
63
-
61
+ _nothing = object ()
64
62
65
- class JsonPointerException (Exception ):
66
- pass
67
63
64
+ def set_pointer (doc , pointer , value , inplace = True ):
65
+ """
66
+ Resolves pointer against doc and sets the value of the target within doc.
68
67
69
- class EndOfList ( object ):
70
- """ Result of accessing element "-" of a list """
68
+ With inplace set to true, doc is modified as long as pointer is not the
69
+ root.
71
70
72
- def __init__ (self , list_ ):
73
- self .list_ = list_
71
+ >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}}
74
72
73
+ >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
74
+ {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
75
+ True
75
76
76
- def __repr__ ( self ):
77
- return '{cls}({lst})' . format ( cls = self . __class__ . __name__ ,
78
- lst = repr ( self . list_ ))
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
79
80
81
+ """
80
82
81
- _nothing = object ()
83
+ pointer = JsonPointer (pointer )
84
+ return pointer .set (doc , value , inplace )
82
85
83
86
84
87
def resolve_pointer (doc , pointer , default = _nothing ):
85
88
"""
86
89
Resolves pointer against doc and returns the referenced object
87
90
88
- >>> obj = {" foo" : {" anArray" : [ {" prop" : 44}], " another prop" : {" baz": " A string" }}}
91
+ >>> obj = {' foo' : {' anArray' : [ {' prop' : 44}], ' another prop' : {' baz': ' A string' }}}
89
92
90
93
>>> resolve_pointer(obj, '') == obj
91
94
True
@@ -110,31 +113,54 @@ def resolve_pointer(doc, pointer, default=_nothing):
110
113
pointer = JsonPointer (pointer )
111
114
return pointer .resolve (doc , default )
112
115
113
- def set_pointer (doc , pointer , value , inplace = True ):
116
+
117
+ def pairwise (iterable ):
114
118
"""
115
- Resolves pointer against doc and sets the value of the target within doc .
119
+ s -> (s0,s1), (s1,s2), (s2, s3), .. .
116
120
117
- With inplace set to true, doc is modified as long as pointer is not the
118
- root.
121
+ >>> list(pairwise([]))
122
+ []
119
123
120
- >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
124
+ >>> list(pairwise([1]))
125
+ []
121
126
122
- >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
123
- {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
124
- True
127
+ >>> list(pairwise([1, 2, 3, 4]))
128
+ [(1, 2), (2, 3), (3, 4)]
129
+ """
130
+ a , b = tee (iterable )
131
+ for _ in b :
132
+ break
133
+ return izip (a , b )
125
134
126
- >>> set_pointer(obj, '/foo/yet%20another%20prop', 'added prop') == \
127
- {'foo': {'another prop': {'baz': 'A string'}, 'yet another prop': 'added prop', 'anArray': [{'prop': 55}]}}
128
- True
129
135
136
+ class JsonPointerException (Exception ):
137
+ pass
138
+
139
+
140
+ class EndOfList (object ):
141
+ """
142
+ Result of accessing element "-" of a list
130
143
"""
131
144
132
- pointer = JsonPointer (pointer )
133
- return pointer .set (doc , value , inplace )
145
+ def __init__ (self , list_ ):
146
+ self .list_ = list_
147
+
148
+ def __repr__ (self ):
149
+ return '{cls}({lst})' .format (cls = self .__class__ .__name__ ,
150
+ lst = repr (self .list_ ))
134
151
135
152
136
153
class JsonPointer (object ):
137
- """ A JSON Pointer that can reference parts of an JSON document """
154
+ """
155
+ A JSON Pointer that can reference parts of an JSON document
156
+ """
157
+ _GETITEM_SUPPORT_ERROR = """document '%s' does not support indexing,
158
+ must be mapping/sequence
159
+ or support __getitem__"""
160
+
161
+ # Array indices must not contain:
162
+ # leading zeros, signs, spaces, decimals, etc
163
+ _RE_ARRAY_INDEX = re .compile ('0|[1-9][0-9]*$' )
138
164
139
165
def __init__ (self , pointer ):
140
166
parts = pointer .split ('/' )
@@ -146,9 +172,10 @@ def __init__(self, pointer):
146
172
parts = [part .replace ('~0' , '~' ) for part in parts ]
147
173
self .parts = parts
148
174
149
-
150
175
def to_last (self , doc ):
151
- """ Resolves ptr until the last step, returns (sub-doc, last-step) """
176
+ """
177
+ Resolves ptr until the last step, returns (sub-doc, last-step)
178
+ """
152
179
153
180
if not self .parts :
154
181
return doc , None
@@ -158,9 +185,10 @@ def to_last(self, doc):
158
185
159
186
return doc , self .get_part (doc , self .parts [- 1 ])
160
187
161
-
162
188
def resolve (self , doc , default = _nothing ):
163
- """Resolves the pointer against doc and returns the referenced object"""
189
+ """
190
+ Resolves the pointer against doc and returns the referenced object
191
+ """
164
192
165
193
for part in self .parts :
166
194
@@ -174,11 +202,12 @@ def resolve(self, doc, default=_nothing):
174
202
175
203
return doc
176
204
177
-
178
205
get = resolve
179
206
180
207
def set (self , doc , value , inplace = True ):
181
- """ Resolve the pointer against the doc and replace the target with value. """
208
+ """
209
+ Resolve the pointer against the doc and replace the target with value.
210
+ """
182
211
183
212
if len (self .parts ) == 0 :
184
213
if inplace :
@@ -194,7 +223,9 @@ def set(self, doc, value, inplace=True):
194
223
return doc
195
224
196
225
def get_part (self , doc , part ):
197
- """ Returns the next step in the correct type """
226
+ """
227
+ Returns the next step in the correct type
228
+ """
198
229
199
230
if isinstance (doc , Mapping ):
200
231
return part
@@ -204,26 +235,28 @@ def get_part(self, doc, part):
204
235
if part == '-' :
205
236
return part
206
237
207
- if not RE_ARRAY_INDEX .match (str (part )):
208
- raise JsonPointerException ("'%s' is not a valid list index" % ( part , ) )
238
+ if not self . _RE_ARRAY_INDEX .match (str (part )):
239
+ raise JsonPointerException ("'%s' is not a valid sequence index" % part )
209
240
210
241
return int (part )
211
242
212
243
elif hasattr (doc , '__getitem__' ):
213
- # Allow indexing via ducktyping if the target has defined __getitem__
244
+ # Allow indexing via ducktyping
245
+ # if the target has defined __getitem__
214
246
return part
215
247
216
248
else :
217
- raise JsonPointerException ("Document '%s' does not support indexing, "
218
- "must be dict/list or support __getitem__" % type (doc ))
219
-
249
+ raise JsonPointerException (self ._GETITEM_SUPPORT_ERROR % type (doc ))
220
250
221
251
def walk (self , doc , part ):
222
- """ Walks one step in doc and returns the referenced part """
252
+ """
253
+ Walks one step in doc and returns the referenced part
254
+ """
223
255
224
256
part = self .get_part (doc , part )
225
257
226
- assert (type (doc ) in (dict , list ) or hasattr (doc , '__getitem__' )), "invalid document type %s" % (type (doc ),)
258
+ assert hasattr (doc , '__getitem__' ), \
259
+ 'invalid document type %s' % type (doc )
227
260
228
261
if isinstance (doc , Mapping ):
229
262
try :
@@ -241,20 +274,23 @@ def walk(self, doc, part):
241
274
return doc [part ]
242
275
243
276
except IndexError :
244
- raise JsonPointerException ("index '%s' is out of bounds" % ( part , ) )
277
+ raise JsonPointerException ("index '%s' is out of bounds" % part )
245
278
246
279
else :
247
280
# Object supports __getitem__, assume custom indexing
248
281
return doc [part ]
249
282
250
283
def contains (self , ptr ):
251
- """ Returns True if self contains the given ptr """
252
- return len (self .parts ) > len (ptr .parts ) and \
253
- self .parts [:len (ptr .parts )] == ptr .parts
284
+ """
285
+ Returns True if self contains the given ptr
286
+ """
287
+ return len (self .parts ) >= len (ptr .parts ) and \
288
+ self .parts [:len (ptr .parts )] == ptr .parts
254
289
255
290
@property
256
291
def path (self ):
257
- """ Returns the string representation of the pointer
292
+ """
293
+ Returns the string representation of the pointer
258
294
259
295
>>> ptr = JsonPointer('/~0/0/~1').path == '/~0/0/~1'
260
296
"""
@@ -263,24 +299,26 @@ def path(self):
263
299
return '' .join ('/' + part for part in parts )
264
300
265
301
def __eq__ (self , other ):
266
- """ compares a pointer to another object
302
+ """
303
+ Compares a pointer to another object
267
304
268
305
Pointers can be compared by comparing their strings (or splitted
269
306
strings), because no two different parts can point to the same
270
- structure in an object (eg no different number representations) """
307
+ structure in an object (eg no different number representations)
308
+ """
271
309
272
310
if not isinstance (other , JsonPointer ):
273
311
return False
274
312
275
313
return self .parts == other .parts
276
314
277
-
278
315
def __hash__ (self ):
279
316
return hash (tuple (self .parts ))
280
317
281
318
@classmethod
282
319
def from_parts (cls , parts ):
283
- """ Constructs a JsonPointer from a list of (unescaped) paths
320
+ """
321
+ Constructs a JsonPointer from a list of (unescaped) paths
284
322
285
323
>>> JsonPointer.from_parts(['a', '~', '/', 0]).path == '/a/~0/~1/0'
286
324
True
@@ -290,22 +328,3 @@ def from_parts(cls, parts):
290
328
parts = [part .replace ('/' , '~1' ) for part in parts ]
291
329
ptr = cls ('' .join ('/' + part for part in parts ))
292
330
return ptr
293
-
294
-
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 )
0 commit comments