1111# are met:
1212#
1313# 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.
1515# 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.
1818# 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.
2020#
2121# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2222# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
3030# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131#
3232
33- from __future__ import unicode_literals
34-
3533""" Identify specific nodes in a JSON document (RFC 6901) """
3634
37- try :
38- from collections .abc import Mapping , Sequence
39- except ImportError :
40- from collections import Mapping , Sequence
35+ from __future__ import unicode_literals
4136
4237# Will be parsed by setup.py to determine package metadata
4338__author__ = 'Stefan Kögl <stefan@skoegl.net>'
44- __version__ = '1.5 '
39+ __version__ = '1.4 '
4540__website__ = 'https://github.com/stefankoegl/python-json-pointer'
4641__license__ = 'Modified BSD License'
4742
4843
4944try :
5045 from urllib import unquote
5146 from itertools import izip
52- except ImportError : # Python 3
47+ except ImportError : # Python 3
5348 from urllib .parse import unquote
5449 izip = zip
5550
51+ try :
52+ from collections .abc import Mapping , Sequence
53+ except ImportError : # Python 3
54+ from collections import Mapping , Sequence
55+
5656from itertools import tee
5757import re
5858import copy
5959
6060
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 ()
6462
65- class JsonPointerException (Exception ):
66- pass
6763
64+ def set_pointer (doc , pointer , value , inplace = True ):
65+ """
66+ Resolves pointer against doc and sets the value of the target within doc.
6867
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.
7170
72- def __init__ (self , list_ ):
73- self .list_ = list_
71+ >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}}
7472
73+ >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
74+ {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
75+ True
7576
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
7980
81+ """
8082
81- _nothing = object ()
83+ pointer = JsonPointer (pointer )
84+ return pointer .set (doc , value , inplace )
8285
8386
8487def resolve_pointer (doc , pointer , default = _nothing ):
8588 """
8689 Resolves pointer against doc and returns the referenced object
8790
88- >>> obj = {" foo" : {" anArray" : [ {" prop" : 44}], " another prop" : {" baz": " A string" }}}
91+ >>> obj = {' foo' : {' anArray' : [ {' prop' : 44}], ' another prop' : {' baz': ' A string' }}}
8992
9093 >>> resolve_pointer(obj, '') == obj
9194 True
@@ -110,31 +113,54 @@ def resolve_pointer(doc, pointer, default=_nothing):
110113 pointer = JsonPointer (pointer )
111114 return pointer .resolve (doc , default )
112115
113- def set_pointer (doc , pointer , value , inplace = True ):
116+
117+ def pairwise (iterable ):
114118 """
115- Resolves pointer against doc and sets the value of the target within doc .
119+ s -> (s0,s1), (s1,s2), (s2, s3), .. .
116120
117- With inplace set to true, doc is modified as long as pointer is not the
118- root.
121+ >>> list(pairwise([]))
122+ []
119123
120- >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
124+ >>> list(pairwise([1]))
125+ []
121126
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 )
125134
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
129135
136+ class JsonPointerException (Exception ):
137+ pass
138+
139+
140+ class EndOfList (object ):
141+ """
142+ Result of accessing element "-" of a list
130143 """
131144
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_ ))
134151
135152
136153class 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]*$' )
138164
139165 def __init__ (self , pointer ):
140166 parts = pointer .split ('/' )
@@ -146,9 +172,10 @@ def __init__(self, pointer):
146172 parts = [part .replace ('~0' , '~' ) for part in parts ]
147173 self .parts = parts
148174
149-
150175 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+ """
152179
153180 if not self .parts :
154181 return doc , None
@@ -158,9 +185,10 @@ def to_last(self, doc):
158185
159186 return doc , self .get_part (doc , self .parts [- 1 ])
160187
161-
162188 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+ """
164192
165193 for part in self .parts :
166194
@@ -174,11 +202,12 @@ def resolve(self, doc, default=_nothing):
174202
175203 return doc
176204
177-
178205 get = resolve
179206
180207 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+ """
182211
183212 if len (self .parts ) == 0 :
184213 if inplace :
@@ -194,7 +223,9 @@ def set(self, doc, value, inplace=True):
194223 return doc
195224
196225 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+ """
198229
199230 if isinstance (doc , Mapping ):
200231 return part
@@ -204,26 +235,28 @@ def get_part(self, doc, part):
204235 if part == '-' :
205236 return part
206237
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 )
209240
210241 return int (part )
211242
212243 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__
214246 return part
215247
216248 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 ))
220250
221251 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+ """
223255
224256 part = self .get_part (doc , part )
225257
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 )
227260
228261 if isinstance (doc , Mapping ):
229262 try :
@@ -241,20 +274,23 @@ def walk(self, doc, part):
241274 return doc [part ]
242275
243276 except IndexError :
244- raise JsonPointerException ("index '%s' is out of bounds" % ( part , ) )
277+ raise JsonPointerException ("index '%s' is out of bounds" % part )
245278
246279 else :
247280 # Object supports __getitem__, assume custom indexing
248281 return doc [part ]
249282
250283 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
254289
255290 @property
256291 def path (self ):
257- """ Returns the string representation of the pointer
292+ """
293+ Returns the string representation of the pointer
258294
259295 >>> ptr = JsonPointer('/~0/0/~1').path == '/~0/0/~1'
260296 """
@@ -263,24 +299,26 @@ def path(self):
263299 return '' .join ('/' + part for part in parts )
264300
265301 def __eq__ (self , other ):
266- """ compares a pointer to another object
302+ """
303+ Compares a pointer to another object
267304
268305 Pointers can be compared by comparing their strings (or splitted
269306 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+ """
271309
272310 if not isinstance (other , JsonPointer ):
273311 return False
274312
275313 return self .parts == other .parts
276314
277-
278315 def __hash__ (self ):
279316 return hash (tuple (self .parts ))
280317
281318 @classmethod
282319 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
284322
285323 >>> JsonPointer.from_parts(['a', '~', '/', 0]).path == '/a/~0/~1/0'
286324 True
@@ -290,22 +328,3 @@ def from_parts(cls, parts):
290328 parts = [part .replace ('/' , '~1' ) for part in parts ]
291329 ptr = cls ('' .join ('/' + part for part in parts ))
292330 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