Codebase list python-json-pointer / d2e3510
Merge tag 'v2.0' into debian/ussuri release v2.0 Michal Arbet 3 years ago
12 changed file(s) with 334 addition(s) and 225 deletion(s). Raw diff Collapse all Expand all
00 language: python
11 python:
2 - "2.6"
3 - "2.7"
4 - "3.3"
5 - "3.4"
6 - "3.5"
7 - "pypy"
8 - "pypy3"
9
2 - '2.7'
3 - '3.4'
4 - '3.5'
5 - '3.6'
6 - 3.6-dev
7 - 3.7-dev
8 - nightly
9 - pypy
10 - pypy3
1011 install:
11 - travis_retry pip install coveralls
12
12 - travis_retry pip install coveralls
1313 script:
14 - coverage run --source=jsonpointer tests.py
15
14 - coverage run --source=jsonpointer tests.py
1615 after_script:
17 - coveralls
18
16 - coveralls
1917 sudo: false
18 addons:
19 apt:
20 packages:
21 - pandoc
22 before_deploy:
23 - pip install -r requirements-dev.txt
24 deploy:
25 provider: pypi
26 user: skoegl
27 password:
28 secure: bKET/1sK+uWetPM3opPTt4qHHfZ6bMcjuLGe3Z/EUfNnGazcbDezy9CHJSqofuMXynF3xc8yluEojjfaqos3Ge/Y4o8pdFMY8ABp8KkxMnAJYGtYzbneSHgdgxPKsmdcUMVtIfioqkeNJTJClWUhRikWSlpKZ7TtkK4AmWtKNwc=
29 on:
30 tags: true
31 distributions: sdist bdist_wheel
+0
-26
COPYING less more
0 Copyright (c) 2011 Stefan Kögl <stefan@skoegl.net>
1 All rights reserved.
2
3 Redistribution and use in source and binary forms, with or without
4 modification, are permitted provided that the following conditions
5 are met:
6
7 1. Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 2. Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 3. The name of the author may not be used to endorse or promote products
13 derived from this software without specific prior written permission.
14
15 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
0 Copyright (c) 2011 Stefan Kögl <stefan@skoegl.net>
1 All rights reserved.
2
3 Redistribution and use in source and binary forms, with or without
4 modification, are permitted provided that the following conditions
5 are met:
6
7 1. Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 2. Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 3. The name of the author may not be used to endorse or promote products
13 derived from this software without specific prior written permission.
14
15 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
00 include AUTHORS
1 include COPYING
1 include LICENSE.txt
22 include README.md
33 include tests.py
0 python-json-pointer [![Build Status](https://secure.travis-ci.org/stefankoegl/python-json-pointer.png?branch=master)](https://travis-ci.org/stefankoegl/python-json-pointer) [![Coverage Status](https://coveralls.io/repos/stefankoegl/python-json-pointer/badge.png?branch=master)](https://coveralls.io/r/stefankoegl/python-json-pointer?branch=master) ![Downloads](https://pypip.in/d/jsonpointer/badge.png) ![Version](https://pypip.in/v/jsonpointer/badge.png)
0 python-json-pointer
11 ===================
2
3 [![PyPI version](https://img.shields.io/pypi/v/jsonpointer.svg)](https://pypi.python.org/pypi/jsonpointer/)
4 [![Supported Python versions](https://img.shields.io/pypi/pyversions/jsonpointer.svg)](https://pypi.python.org/pypi/jsonpointer/)
5 [![Build Status](https://travis-ci.org/stefankoegl/python-json-pointer.png?branch=master)](https://travis-ci.org/stefankoegl/python-json-pointer)
6 [![Coverage Status](https://coveralls.io/repos/stefankoegl/python-json-pointer/badge.png?branch=master)](https://coveralls.io/r/stefankoegl/python-json-pointer?branch=master)
7
28
39 Resolve JSON Pointers in Python
410 -------------------------------
612 Library to resolve JSON Pointers according to
713 [RFC 6901](http://tools.ietf.org/html/rfc6901)
814
9 See Sourcecode for Examples
15 See source code for examples
1016 * Website: https://github.com/stefankoegl/python-json-pointer
1117 * Repository: https://github.com/stefankoegl/python-json-pointer.git
1218 * Documentation: https://python-json-pointer.readthedocs.org/
1319 * PyPI: https://pypi.python.org/pypi/jsonpointer
14 * Travis-CI: https://travis-ci.org/stefankoegl/python-json-pointer
20 * Travis CI: https://travis-ci.org/stefankoegl/python-json-pointer
1521 * Coveralls: https://coveralls.io/r/stefankoegl/python-json-pointer
1111
1212 parser = argparse.ArgumentParser(
1313 description='Resolve a JSON pointer on JSON files')
14 parser.add_argument('POINTER', type=argparse.FileType('r'),
15 help='File containing a JSON pointer expression')
14
15 # Accept pointer as argument or as file
16 ptr_group = parser.add_mutually_exclusive_group(required=True)
17
18 ptr_group.add_argument('-f', '--pointer-file', type=argparse.FileType('r'),
19 nargs='?',
20 help='File containing a JSON pointer expression')
21
22 ptr_group.add_argument('POINTER', type=str, nargs='?',
23 help='A JSON pointer expression')
24
1625 parser.add_argument('FILE', type=argparse.FileType('r'), nargs='+',
1726 help='Files for which the pointer should be resolved')
1827 parser.add_argument('--indent', type=int, default=None,
2837 sys.exit(1)
2938
3039
40 def parse_pointer(args):
41 if args.POINTER:
42 ptr = args.POINTER
43 elif args.pointer_file:
44 ptr = args.pointer_file.read().strip()
45 else:
46 parser.print_usage()
47 sys.exit(1)
48
49 return ptr
50
51
3152 def resolve_files():
3253 """ Resolve a JSON pointer on JSON files """
3354 args = parser.parse_args()
34 ptr = json.load(args.POINTER)
55
56 ptr = parse_pointer(args)
57
3558 for f in args.FILE:
3659 doc = json.load(f)
3760 try:
66 ===================
77
88 *python-json-pointer* is a Python library for resolving JSON pointers (`RFC
9 6901 <http://tools.ietf.org/html/rfc6901>`_). Python 2.6, 2.7, 3.2, 3.3
9 6901 <http://tools.ietf.org/html/rfc6901>`_). Python 2.7, 3.4+
1010 and PyPy are supported.
1111
1212 **Contents**
1010 # are met:
1111 #
1212 # 1. Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
13 # notice, this list of conditions and the following disclaimer.
1414 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
1717 # 3. The name of the author may not be used to endorse or promote products
18 # derived from this software without specific prior written permission.
18 # derived from this software without specific prior written permission.
1919 #
2020 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2121 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2929 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3030 #
3131
32 """ Identify specific nodes in a JSON document (RFC 6901) """
33
3234 from __future__ import unicode_literals
3335
34 """ Identify specific nodes in a JSON document (RFC 6901) """
36 # Will be parsed by setup.py to determine package metadata
37 __author__ = 'Stefan Kögl <stefan@skoegl.net>'
38 __version__ = '2.0'
39 __website__ = 'https://github.com/stefankoegl/python-json-pointer'
40 __license__ = 'Modified BSD License'
41
42
43 try:
44 from itertools import izip
45 str = unicode
46 except ImportError: # Python 3
47 izip = zip
3548
3649 try:
3750 from collections.abc import Mapping, Sequence
38 except ImportError:
51 except ImportError: # Python 3
3952 from collections import Mapping, Sequence
40
41 # Will be parsed by setup.py to determine package metadata
42 __author__ = 'Stefan Kögl <stefan@skoegl.net>'
43 __version__ = '1.10'
44 __website__ = 'https://github.com/stefankoegl/python-json-pointer'
45 __license__ = 'Modified BSD License'
46
47
48 try:
49 from urllib import unquote
50 from itertools import izip
51 str = unicode
52 except ImportError: # Python 3
53 from urllib.parse import unquote
54 izip = zip
5553
5654 from itertools import tee
5755 import re
5856 import copy
5957
6058
61 # array indices must not contain leading zeros, signs, spaces, decimals, etc
62 RE_ARRAY_INDEX=re.compile('0|[1-9][0-9]*$')
59 _nothing = object()
60
61
62 def set_pointer(doc, pointer, value, inplace=True):
63 """Resolves pointer against doc and sets the value of the target within doc.
64
65 With inplace set to true, doc is modified as long as pointer is not the
66 root.
67
68 >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}}
69
70 >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
71 {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
72 True
73
74 >>> set_pointer(obj, '/foo/yet another prop', 'added prop') == \
75 {'foo': {'another prop': {'baz': 'A string'}, 'yet another prop': 'added prop', 'anArray': [{'prop': 55}]}}
76 True
77
78 >>> obj = {'foo': {}}
79 >>> set_pointer(obj, '/foo/a%20b', 'x') == \
80 {'foo': {'a%20b': 'x' }}
81 True
82 """
83
84 pointer = JsonPointer(pointer)
85 return pointer.set(doc, value, inplace)
86
87
88 def resolve_pointer(doc, pointer, default=_nothing):
89 """ Resolves pointer against doc and returns the referenced object
90
91 >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}, 'a%20b': 1, 'c d': 2}
92
93 >>> resolve_pointer(obj, '') == obj
94 True
95
96 >>> resolve_pointer(obj, '/foo') == obj['foo']
97 True
98
99 >>> resolve_pointer(obj, '/foo/another prop') == obj['foo']['another prop']
100 True
101
102 >>> resolve_pointer(obj, '/foo/another prop/baz') == obj['foo']['another prop']['baz']
103 True
104
105 >>> resolve_pointer(obj, '/foo/anArray/0') == obj['foo']['anArray'][0]
106 True
107
108 >>> resolve_pointer(obj, '/some/path', None) == None
109 True
110
111 >>> resolve_pointer(obj, '/a b', None) == None
112 True
113
114 >>> resolve_pointer(obj, '/a%20b') == 1
115 True
116
117 >>> resolve_pointer(obj, '/c d') == 2
118 True
119
120 >>> resolve_pointer(obj, '/c%20d', None) == None
121 True
122 """
123
124 pointer = JsonPointer(pointer)
125 return pointer.resolve(doc, default)
126
127
128 def pairwise(iterable):
129 """ Transforms a list to a list of tuples of adjacent items
130
131 s -> (s0,s1), (s1,s2), (s2, s3), ...
132
133 >>> list(pairwise([]))
134 []
135
136 >>> list(pairwise([1]))
137 []
138
139 >>> list(pairwise([1, 2, 3, 4]))
140 [(1, 2), (2, 3), (3, 4)]
141 """
142 a, b = tee(iterable)
143 for _ in b:
144 break
145 return izip(a, b)
63146
64147
65148 class JsonPointerException(Exception):
67150
68151
69152 class EndOfList(object):
70 """ Result of accessing element "-" of a list """
153 """Result of accessing element "-" of a list"""
71154
72155 def __init__(self, list_):
73156 self.list_ = list_
74157
75
76158 def __repr__(self):
77159 return '{cls}({lst})'.format(cls=self.__class__.__name__,
78 lst=repr(self.list_))
79
80
81 _nothing = object()
82
83
84 def resolve_pointer(doc, pointer, default=_nothing):
85 """
86 Resolves pointer against doc and returns the referenced object
87
88 >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
89
90 >>> resolve_pointer(obj, '') == obj
91 True
92
93 >>> resolve_pointer(obj, '/foo') == obj['foo']
94 True
95
96 >>> resolve_pointer(obj, '/foo/another%20prop') == obj['foo']['another prop']
97 True
98
99 >>> resolve_pointer(obj, '/foo/another%20prop/baz') == obj['foo']['another prop']['baz']
100 True
101
102 >>> resolve_pointer(obj, '/foo/anArray/0') == obj['foo']['anArray'][0]
103 True
104
105 >>> resolve_pointer(obj, '/some/path', None) == None
106 True
107
108 """
109
110 pointer = JsonPointer(pointer)
111 return pointer.resolve(doc, default)
112
113 def set_pointer(doc, pointer, value, inplace=True):
114 """
115 Resolves pointer against doc and sets the value of the target within doc.
116
117 With inplace set to true, doc is modified as long as pointer is not the
118 root.
119
120 >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
121
122 >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
123 {'foo': {'another prop': {'baz': 'A string'}, 'anArray': [{'prop': 55}]}}
124 True
125
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
130 """
131
132 pointer = JsonPointer(pointer)
133 return pointer.set(doc, value, inplace)
160 lst=repr(self.list_))
134161
135162
136163 class JsonPointer(object):
137 """ A JSON Pointer that can reference parts of an JSON document """
164 """A JSON Pointer that can reference parts of an JSON document"""
165
166 # Array indices must not contain:
167 # leading zeros, signs, spaces, decimals, etc
168 _RE_ARRAY_INDEX = re.compile('0|[1-9][0-9]*$')
169 _RE_INVALID_ESCAPE = re.compile('(~[^01]|~$)')
138170
139171 def __init__(self, pointer):
172
173 # validate escapes
174 invalid_escape = self._RE_INVALID_ESCAPE.search(pointer)
175 if invalid_escape:
176 raise JsonPointerException('Found invalid escape {}'.format(
177 invalid_escape.group()))
178
140179 parts = pointer.split('/')
141180 if parts.pop(0) != '':
142181 raise JsonPointerException('location must starts with /')
143182
144 parts = map(unquote, parts)
145 parts = [part.replace('~1', '/') for part in parts]
146 parts = [part.replace('~0', '~') for part in parts]
183 parts = [unescape(part) for part in parts]
147184 self.parts = parts
148185
149
150186 def to_last(self, doc):
151 """ Resolves ptr until the last step, returns (sub-doc, last-step) """
187 """Resolves ptr until the last step, returns (sub-doc, last-step)"""
152188
153189 if not self.parts:
154190 return doc, None
157193 doc = self.walk(doc, part)
158194
159195 return doc, self.get_part(doc, self.parts[-1])
160
161196
162197 def resolve(self, doc, default=_nothing):
163198 """Resolves the pointer against doc and returns the referenced object"""
174209
175210 return doc
176211
177
178212 get = resolve
179213
180214 def set(self, doc, value, inplace=True):
181 """ Resolve the pointer against the doc and replace the target with value. """
215 """Resolve the pointer against the doc and replace the target with value."""
182216
183217 if len(self.parts) == 0:
184218 if inplace:
194228 return doc
195229
196230 def get_part(self, doc, part):
197 """ Returns the next step in the correct type """
231 """Returns the next step in the correct type"""
198232
199233 if isinstance(doc, Mapping):
200234 return part
204238 if part == '-':
205239 return part
206240
207 if not RE_ARRAY_INDEX.match(str(part)):
208 raise JsonPointerException("'%s' is not a valid list index" % (part, ))
241 if not self._RE_ARRAY_INDEX.match(str(part)):
242 raise JsonPointerException("'%s' is not a valid sequence index" % part)
209243
210244 return int(part)
211245
212246 elif hasattr(doc, '__getitem__'):
213 # Allow indexing via ducktyping if the target has defined __getitem__
247 # Allow indexing via ducktyping
248 # if the target has defined __getitem__
214249 return part
215250
216251 else:
217252 raise JsonPointerException("Document '%s' does not support indexing, "
218 "must be dict/list or support __getitem__" % type(doc))
253 "must be mapping/sequence or support __getitem__" % type(doc))
219254
220255
221256 def walk(self, doc, part):
223258
224259 part = self.get_part(doc, part)
225260
226 assert (type(doc) in (dict, list) or hasattr(doc, '__getitem__')), "invalid document type %s" % (type(doc),)
227
228 if isinstance(doc, Mapping):
261 assert hasattr(doc, '__getitem__'), "invalid document type %s" % (type(doc),)
262
263 if isinstance(doc, Sequence):
264 if part == '-':
265 return EndOfList(doc)
266
229267 try:
230268 return doc[part]
231269
232 except KeyError:
233 raise JsonPointerException("member '%s' not found in %s" % (part, doc))
234
235 elif isinstance(doc, Sequence):
236
237 if part == '-':
238 return EndOfList(doc)
239
240 try:
241 return doc[part]
242
243270 except IndexError:
244271 raise JsonPointerException("index '%s' is out of bounds" % (part, ))
245272
246 else:
247 # Object supports __getitem__, assume custom indexing
273 # Else the object is a mapping or supports __getitem__(so assume custom indexing)
274 try:
248275 return doc[part]
249276
277 except KeyError:
278 raise JsonPointerException("member '%s' not found in %s" % (part, doc))
279
280
250281 def contains(self, ptr):
251 """Returns True if self contains the given ptr"""
282 """ Returns True if self contains the given ptr """
252283 return self.parts[:len(ptr.parts)] == ptr.parts
253284
254285 def __contains__(self, item):
255 """Returns True if self contains the given ptr"""
286 """ Returns True if self contains the given ptr """
256287 return self.contains(item)
257288
258289 @property
259290 def path(self):
260 """ Returns the string representation of the pointer
291 """Returns the string representation of the pointer
261292
262293 >>> ptr = JsonPointer('/~0/0/~1').path == '/~0/0/~1'
263294 """
264 parts = [part.replace('~', '~0') for part in self.parts]
265 parts = [part.replace('/', '~1') for part in parts]
295 parts = [escape(part) for part in self.parts]
266296 return ''.join('/' + part for part in parts)
267297
268298 def __eq__(self, other):
269 """ compares a pointer to another object
299 """Compares a pointer to another object
270300
271301 Pointers can be compared by comparing their strings (or splitted
272302 strings), because no two different parts can point to the same
273 structure in an object (eg no different number representations) """
303 structure in an object (eg no different number representations)
304 """
274305
275306 if not isinstance(other, JsonPointer):
276307 return False
277308
278309 return self.parts == other.parts
279310
280
281311 def __hash__(self):
282312 return hash(tuple(self.parts))
283313
284314 @classmethod
285315 def from_parts(cls, parts):
286 """ Constructs a JsonPointer from a list of (unescaped) paths
316 """Constructs a JsonPointer from a list of (unescaped) paths
287317
288318 >>> JsonPointer.from_parts(['a', '~', '/', 0]).path == '/a/~0/~1/0'
289319 True
290320 """
291 parts = [str(part) for part in parts]
292 parts = [part.replace('~', '~0') for part in parts]
293 parts = [part.replace('/', '~1') for part in parts]
321 parts = [escape(str(part)) for part in parts]
294322 ptr = cls(''.join('/' + part for part in parts))
295323 return ptr
296324
297325
298
299 def pairwise(iterable):
300 """ s -> (s0,s1), (s1,s2), (s2, s3), ...
301
302 >>> list(pairwise([]))
303 []
304
305 >>> list(pairwise([1]))
306 []
307
308 >>> list(pairwise([1, 2, 3, 4]))
309 [(1, 2), (2, 3), (3, 4)]
310 """
311 a, b = tee(iterable)
312 for _ in b:
313 break
314 return izip(a, b)
326 def escape(s):
327 return s.replace('~', '~0').replace('/', '~1')
328
329 def unescape(s):
330 return s.replace('~1', '/').replace('~0', '~')
00 wheel
1 pandoc==1.0.0-alpha.3
1 pypandoc==1.4
0 [bdist_wheel]
1 universal = 1
4141 'Operating System :: OS Independent',
4242 'Programming Language :: Python',
4343 'Programming Language :: Python :: 2',
44 'Programming Language :: Python :: 2.6',
4544 'Programming Language :: Python :: 2.7',
4645 'Programming Language :: Python :: 3',
47 'Programming Language :: Python :: 3.3',
4846 'Programming Language :: Python :: 3.4',
4947 'Programming Language :: Python :: 3.5',
48 'Programming Language :: Python :: 3.6',
5049 'Programming Language :: Python :: Implementation :: CPython',
5150 'Programming Language :: Python :: Implementation :: PyPy',
5251 'Topic :: Software Development :: Libraries',
6463 py_modules=MODULES,
6564 scripts=['bin/jsonpointer'],
6665 classifiers=CLASSIFIERS,
66 python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
6767 )
88 import copy
99 from jsonpointer import resolve_pointer, EndOfList, JsonPointerException, \
1010 JsonPointer, set_pointer
11
1112
1213 class SpecificationTests(unittest.TestCase):
1314 """ Tests all examples from the JSON Pointer specification """
7273 new_ptr = JsonPointer.from_parts(parts)
7374 self.assertEqual(ptr, new_ptr)
7475
76
7577 class ComparisonTests(unittest.TestCase):
7678
7779 def setUp(self):
107109 self.assertTrue(self.ptr1 in self.ptr1)
108110 self.assertFalse(self.ptr3 in self.ptr1)
109111
110
111112 class WrongInputTests(unittest.TestCase):
112113
113114 def test_no_start_slash(self):
123124 # this list does not have 10 members
124125 doc = [0, 1, 2]
125126 self.assertRaises(JsonPointerException, resolve_pointer, doc, '/10')
127
128 def test_trailing_escape(self):
129 self.assertRaises(JsonPointerException, JsonPointer, '/foo/bar~')
130
131 def test_invalid_escape(self):
132 self.assertRaises(JsonPointerException, JsonPointer, '/foo/bar~2')
126133
127134
128135 class ToLastTests(unittest.TestCase):
191198
192199 self.assertRaises(JsonPointerException, set_pointer, doc, "", 9)
193200
201
194202 class AltTypesTests(unittest.TestCase):
195203
204 class Node(object):
205 def __init__(self, name, parent=None):
206 self.name = name
207 self.parent = parent
208 self.left = None
209 self.right = None
210
211 def set_left(self, node):
212 node.parent = self
213 self.left = node
214
215 def set_right(self, node):
216 node.parent = self
217 self.right = node
218
219 def __getitem__(self, key):
220 if key == 'left':
221 return self.left
222 if key == 'right':
223 return self.right
224
225 raise KeyError("Only left and right supported")
226
227 def __setitem__(self, key, val):
228 if key == 'left':
229 return self.set_left(val)
230 if key == 'right':
231 return self.set_right(val)
232
233 raise KeyError("Only left and right supported: %s" % key)
234
235 class mdict(object):
236 def __init__(self, d):
237 self._d = d
238 def __getitem__(self, item):
239 return self._d[item]
240
241 mdict = mdict({'root': {'1': {'2': '3'}}})
242 Node = Node
243
244
196245 def test_alttypes(self):
197 JsonPointer.alttypes = True
198
199 class Node(object):
200 def __init__(self, name, parent=None):
201 self.name = name
202 self.parent = parent
203 self.left = None
204 self.right = None
205
206 def set_left(self, node):
207 node.parent = self
208 self.left = node
209
210 def set_right(self, node):
211 node.parent = self
212 self.right = node
213
214 def __getitem__(self, key):
215 if key == 'left':
216 return self.left
217 if key == 'right':
218 return self.right
219
220 raise KeyError("Only left and right supported")
221
222 def __setitem__(self, key, val):
223 if key == 'left':
224 return self.set_left(val)
225 if key == 'right':
226 return self.set_right(val)
227
228 raise KeyError("Only left and right supported: %s" % key)
229
246 Node = self.Node
230247
231248 root = Node('root')
232249 root.set_left(Node('a'))
248265 set_pointer(root, '/left/right', Node('AB'))
249266 self.assertEqual(resolve_pointer(root, '/left/right').name, 'AB')
250267
268 def test_mock_dict_sanity(self):
269 doc = self.mdict
270 default = None
271
272 # TODO: Generate this automatically for any given object
273 path_to_expected_value = {
274 '/root/1': {'2': '3'},
275 '/root': {'1': {'2': '3'}},
276 '/root/1/2': '3',
277 }
278
279 for path, expected_value in iter(path_to_expected_value.items()):
280 self.assertEqual(resolve_pointer(doc, path, default), expected_value)
281
282 def test_mock_dict_returns_default(self):
283 doc = self.mdict
284 default = None
285
286 path_to_expected_value = {
287 '/foo': default,
288 '/x/y/z/d': default
289 }
290
291 for path, expected_value in iter(path_to_expected_value.items()):
292 self.assertEqual(resolve_pointer(doc, path, default), expected_value)
293
294 def test_mock_dict_raises_key_error(self):
295 doc = self.mdict
296 self.assertRaises(JsonPointerException, resolve_pointer, doc, '/foo')
297 self.assertRaises(JsonPointerException, resolve_pointer, doc, '/root/1/2/3/4')
298
299
300
251301 suite = unittest.TestSuite()
252302 suite.addTest(unittest.makeSuite(SpecificationTests))
253303 suite.addTest(unittest.makeSuite(ComparisonTests))