jsonpointer 3.1.1__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonpointer
3
- Version: 3.1.1
3
+ Version: 3.2.0
4
4
  Summary: Identify specific nodes in a JSON document (RFC 6901)
5
5
  Home-page: https://github.com/stefankoegl/python-json-pointer
6
6
  Author: Stefan Kögl
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+
8
+ import jsonpointer
9
+
10
+ parser = argparse.ArgumentParser(
11
+ description='Resolve a JSON pointer on JSON files')
12
+
13
+ # Accept pointer as option, as file or as first positional argument
14
+ ptr_group = parser.add_mutually_exclusive_group()
15
+
16
+ ptr_group.add_argument('-f', '--pointer-file', type=argparse.FileType('r'),
17
+ help='File containing a JSON pointer expression')
18
+
19
+ ptr_group.add_argument('-p', '--pointer', type=str,
20
+ help='A JSON pointer expression')
21
+
22
+ parser.add_argument('POINTER', type=str, nargs='?',
23
+ help='A JSON pointer expression (if neither -f nor -p '
24
+ 'is given)')
25
+
26
+ parser.add_argument('FILE', type=str, nargs='+',
27
+ help='Files for which the pointer should be resolved')
28
+ parser.add_argument('--indent', type=int, default=None,
29
+ help='Indent output by n spaces')
30
+ parser.add_argument('-v', '--version', action='version',
31
+ version='%(prog)s ' + jsonpointer.__version__)
32
+
33
+
34
+ def main():
35
+ try:
36
+ ok = resolve_files()
37
+ except KeyboardInterrupt:
38
+ sys.exit(1)
39
+
40
+ if not ok:
41
+ sys.exit(1)
42
+
43
+
44
+ def parse_pointer(args):
45
+ """ Return the pointer and the list of files to resolve it on
46
+
47
+ If the pointer is given via -f or -p, a POINTER positional argument is
48
+ actually the first file. """
49
+ files = args.FILE
50
+
51
+ if args.pointer is not None or args.pointer_file is not None:
52
+ if args.POINTER is not None:
53
+ files = [args.POINTER] + files
54
+
55
+ if args.pointer is not None:
56
+ ptr = args.pointer
57
+ else:
58
+ ptr = args.pointer_file.read().strip()
59
+
60
+ elif args.POINTER is not None:
61
+ ptr = args.POINTER
62
+
63
+ else:
64
+ parser.error('a JSON pointer is required (POINTER, -p or -f)')
65
+
66
+ # Catches e.g. `jsonpointer a.json b.json`, where the pointer was omitted
67
+ # and the first file would otherwise be used as the pointer
68
+ try:
69
+ jsonpointer.JsonPointer(ptr)
70
+ except jsonpointer.JsonPointerException as e:
71
+ parser.error('invalid JSON pointer %r: %s' % (ptr, e))
72
+
73
+ return ptr, files
74
+
75
+
76
+ def resolve_files():
77
+ """ Resolve a JSON pointer on JSON files
78
+
79
+ Returns False if the pointer could not be resolved on at least one
80
+ file. """
81
+ args = parser.parse_args()
82
+
83
+ ptr, files = parse_pointer(args)
84
+ ok = True
85
+
86
+ for name in files:
87
+ try:
88
+ f = argparse.FileType('r')(name)
89
+ except argparse.ArgumentTypeError as e:
90
+ parser.error(str(e))
91
+
92
+ doc = json.load(f)
93
+ try:
94
+ result = jsonpointer.resolve_pointer(doc, ptr)
95
+ print(json.dumps(result, indent=args.indent))
96
+ except jsonpointer.JsonPointerException as e:
97
+ print('Could not resolve pointer: %s' % str(e), file=sys.stderr)
98
+ ok = False
99
+
100
+ return ok
101
+
102
+
103
+ if __name__ == "__main__":
104
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonpointer
3
- Version: 3.1.1
3
+ Version: 3.2.0
4
4
  Summary: Identify specific nodes in a JSON document (RFC 6901)
5
5
  Home-page: https://github.com/stefankoegl/python-json-pointer
6
6
  Author: Stefan Kögl
@@ -32,7 +32,7 @@
32
32
 
33
33
  # Will be parsed by setup.py to determine package metadata
34
34
  __author__ = 'Stefan Kögl <stefan@skoegl.net>'
35
- __version__ = '3.1.1'
35
+ __version__ = '3.2.0'
36
36
  __website__ = 'https://github.com/stefankoegl/python-json-pointer'
37
37
  __license__ = 'Modified BSD License'
38
38
 
@@ -43,6 +43,10 @@ from itertools import tee, chain
43
43
 
44
44
  _nothing = object()
45
45
 
46
+ # If True, exceptions include the doc content in the error message.
47
+ # Set to False to suppress potentially large document content from exceptions.
48
+ VERBOSE_EXCEPTIONS = True
49
+
46
50
 
47
51
  def set_pointer(doc, pointer, value, inplace=True):
48
52
  """Resolves a pointer against doc and sets the value of the target within doc.
@@ -209,10 +213,15 @@ class JsonPointer:
209
213
 
210
214
  (parent, part) = self.to_last(doc)
211
215
 
212
- if isinstance(parent, Sequence) and part == '-':
216
+ if isinstance(parent, str):
217
+ raise JsonPointerException("Cannot set value in a string")
218
+ elif isinstance(parent, Sequence) and part == '-':
213
219
  parent.append(value)
214
220
  else:
215
- parent[part] = value
221
+ try:
222
+ parent[part] = value
223
+ except (TypeError, IndexError) as e:
224
+ raise JsonPointerException("Invalid assignment target: %s" % (e,))
216
225
 
217
226
  return doc
218
227
 
@@ -223,6 +232,9 @@ class JsonPointer:
223
232
  if isinstance(doc, Mapping):
224
233
  return part
225
234
 
235
+ elif isinstance(doc, str):
236
+ raise JsonPointerException("Cannot apply token '%s' to non-container type %s" % (part, type(doc)))
237
+
226
238
  elif isinstance(doc, Sequence):
227
239
 
228
240
  if part == '-':
@@ -231,7 +243,10 @@ class JsonPointer:
231
243
  if not JsonPointer._RE_ARRAY_INDEX.fullmatch(str(part)):
232
244
  raise JsonPointerException("'%s' is not a valid sequence index" % part)
233
245
 
234
- return int(part)
246
+ try:
247
+ return int(part)
248
+ except ValueError as e:
249
+ raise JsonPointerException("Invalid sequence index: %s" % (e,))
235
250
 
236
251
  elif hasattr(doc, '__getitem__'):
237
252
  # Allow indexing via ducktyping
@@ -269,7 +284,10 @@ class JsonPointer:
269
284
  return doc[part]
270
285
 
271
286
  except KeyError:
272
- raise JsonPointerException("member '%s' not found in %s" % (part, doc))
287
+ if VERBOSE_EXCEPTIONS:
288
+ raise JsonPointerException("member '%s' not found in %s" % (part, doc))
289
+ else:
290
+ raise JsonPointerException("member '%s' not found" % (part,))
273
291
 
274
292
  def contains(self, ptr):
275
293
  """ Returns True if self contains the given ptr """
@@ -288,7 +306,7 @@ class JsonPointer:
288
306
  else:
289
307
  suffix_parts = suffix
290
308
  try:
291
- return JsonPointer.from_parts(chain(self.parts, suffix_parts))
309
+ return self.__class__.from_parts(chain(self.parts, suffix_parts))
292
310
  except: # noqa E722
293
311
  raise JsonPointerException("Invalid suffix")
294
312
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  import copy
4
4
  import doctest
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ import tempfile
5
9
  import unittest
6
10
 
7
11
  import jsonpointer
@@ -192,6 +196,31 @@ class ComparisonTests(unittest.TestCase):
192
196
  ptr12e = self.ptr1 / ["a", "b"]
193
197
  self.assertEqual(ptr12e.path, "/a/b/c/a/b")
194
198
 
199
+ def test_join_subclass(self):
200
+ class Pointer(JsonPointer):
201
+ pass
202
+
203
+ class OtherPointer(JsonPointer):
204
+ pass
205
+
206
+ ptr = Pointer("/a~1b")
207
+ suffixes = [JsonPointer("/m~0n"), Pointer("/m~0n"),
208
+ OtherPointer("/m~0n"), "/m~0n", ["m~n"]]
209
+ for suffix in suffixes:
210
+ with self.subTest(suffix=suffix):
211
+ joined = ptr.join(suffix)
212
+ self.assertIs(type(joined), Pointer)
213
+ self.assertEqual(joined.path, "/a~1b/m~0n")
214
+ self.assertEqual(ptr.path, "/a~1b")
215
+
216
+ def test_join_magic_subclass(self):
217
+ class Pointer(JsonPointer):
218
+ pass
219
+
220
+ ptr = Pointer("/a") / JsonPointer("/b") / ["c"]
221
+ self.assertIs(type(ptr), Pointer)
222
+ self.assertEqual(ptr.path, "/a/b/c")
223
+
195
224
 
196
225
  class WrongInputTests(unittest.TestCase):
197
226
 
@@ -219,6 +248,54 @@ class WrongInputTests(unittest.TestCase):
219
248
  doc = [0, 1, 2]
220
249
  self.assertRaises(JsonPointerException, resolve_pointer, doc, '/01')
221
250
 
251
+ def test_string_not_indexable(self):
252
+ doc = {"foo": "should-not-be-indexable"}
253
+ self.assertRaises(JsonPointerException, resolve_pointer, doc, "/foo/0")
254
+
255
+ ptr = JsonPointer("/foo/0")
256
+ self.assertRaises(JsonPointerException, ptr.resolve, doc)
257
+
258
+
259
+ @unittest.skipUnless(hasattr(sys, 'get_int_max_str_digits'),
260
+ 'Integer string conversion limits are not available')
261
+ class LargeIndexTests(unittest.TestCase):
262
+
263
+ def setUp(self):
264
+ limit = sys.get_int_max_str_digits()
265
+ if not limit:
266
+ self.skipTest('Integer string conversion limit is disabled')
267
+ self.token = '1' * (limit + 1)
268
+ self.pointer = '/' + self.token
269
+
270
+ def test_resolve_large_index(self):
271
+ self.assertRaises(JsonPointerException, resolve_pointer,
272
+ [0], self.pointer)
273
+
274
+ def test_resolve_large_index_default(self):
275
+ default = object()
276
+ self.assertIs(resolve_pointer([0], self.pointer, default), default)
277
+ self.assertIs(resolve_pointer({'items': [0]},
278
+ '/items' + self.pointer, default),
279
+ default)
280
+
281
+ def test_to_last_large_index(self):
282
+ self.assertRaises(JsonPointerException,
283
+ JsonPointer(self.pointer).to_last, [0])
284
+
285
+ def test_set_large_index(self):
286
+ for inplace in (True, False):
287
+ with self.subTest(inplace=inplace):
288
+ doc = [0]
289
+ self.assertRaises(JsonPointerException, set_pointer,
290
+ doc, self.pointer, 1, inplace=inplace)
291
+ self.assertEqual(doc, [0])
292
+
293
+ def test_large_numeric_object_member(self):
294
+ doc = {self.token: 'old'}
295
+ self.assertEqual(resolve_pointer(doc, self.pointer), 'old')
296
+ set_pointer(doc, self.pointer, 'new')
297
+ self.assertEqual(doc, {self.token: 'new'})
298
+
222
299
 
223
300
  class ToLastTests(unittest.TestCase):
224
301
 
@@ -394,6 +471,135 @@ class AltTypesTests(unittest.TestCase):
394
471
  self.assertRaises(JsonPointerException, resolve_pointer, doc, '/root/1/2/3/4')
395
472
 
396
473
 
474
+ class CommandLineTests(unittest.TestCase):
475
+ """ Tests the jsonpointer command line utility """
476
+
477
+ ROOT = os.path.dirname(os.path.abspath(__file__))
478
+ SCRIPT = os.path.join(ROOT, 'bin', 'jsonpointer')
479
+
480
+ A_OUT = '[1, 2, 3]\n'
481
+ B_OUT = '{"b": [1, 3, 4]}\n'
482
+
483
+ def setUp(self):
484
+ tmpdir = tempfile.TemporaryDirectory()
485
+ self.addCleanup(tmpdir.cleanup)
486
+ self.dir = tmpdir.name
487
+
488
+ self._write('a.json', '{ "a": [1, 2, 3] }')
489
+ self._write('b.json', '{ "a": {"b": [1, 3, 4]}, "b": 1 }')
490
+ self._write('ptr.txt', '/a\n')
491
+
492
+ def _write(self, name, content):
493
+ with open(os.path.join(self.dir, name), 'w') as f:
494
+ f.write(content)
495
+
496
+ def _run(self, *args):
497
+ env = dict(os.environ, PYTHONPATH=self.ROOT)
498
+ return subprocess.run([sys.executable, self.SCRIPT] + list(args),
499
+ cwd=self.dir, env=env, capture_output=True,
500
+ text=True)
501
+
502
+ def test_positional_pointer(self):
503
+ proc = self._run('/a', 'a.json', 'b.json')
504
+ self.assertEqual(proc.returncode, 0, proc.stderr)
505
+ self.assertEqual(proc.stdout, self.A_OUT + self.B_OUT)
506
+
507
+ def test_positional_pointer_single_file(self):
508
+ proc = self._run('/a', 'a.json')
509
+ self.assertEqual(proc.returncode, 0, proc.stderr)
510
+ self.assertEqual(proc.stdout, self.A_OUT)
511
+
512
+ def test_pointer_file(self):
513
+ proc = self._run('-f', 'ptr.txt', 'a.json', 'b.json')
514
+ self.assertEqual(proc.returncode, 0, proc.stderr)
515
+ self.assertEqual(proc.stdout, self.A_OUT + self.B_OUT)
516
+
517
+ def test_pointer_file_single_file(self):
518
+ proc = self._run('-f', 'ptr.txt', 'a.json')
519
+ self.assertEqual(proc.returncode, 0, proc.stderr)
520
+ self.assertEqual(proc.stdout, self.A_OUT)
521
+
522
+ def test_pointer_option(self):
523
+ proc = self._run('-p', '/a', 'a.json', 'b.json')
524
+ self.assertEqual(proc.returncode, 0, proc.stderr)
525
+ self.assertEqual(proc.stdout, self.A_OUT + self.B_OUT)
526
+
527
+ def test_no_pointer(self):
528
+ proc = self._run('a.json')
529
+ self.assertEqual(proc.returncode, 2)
530
+ self.assertIn('a JSON pointer is required', proc.stderr)
531
+
532
+ def test_missing_pointer_multiple_files(self):
533
+ proc = self._run('a.json', 'b.json')
534
+ self.assertEqual(proc.returncode, 2)
535
+ self.assertIn("invalid JSON pointer 'a.json'", proc.stderr)
536
+ self.assertEqual(proc.stdout, '')
537
+
538
+ def test_invalid_pointer_file(self):
539
+ self._write('bad.txt', 'a\n')
540
+ proc = self._run('-f', 'bad.txt', 'a.json')
541
+ self.assertEqual(proc.returncode, 2)
542
+ self.assertIn("invalid JSON pointer 'a'", proc.stderr)
543
+
544
+ def test_empty_pointer(self):
545
+ proc = self._run('', 'a.json')
546
+ self.assertEqual(proc.returncode, 0, proc.stderr)
547
+ self.assertEqual(proc.stdout, '{"a": [1, 2, 3]}\n')
548
+
549
+ def test_unresolvable_pointer(self):
550
+ proc = self._run('/x', 'a.json')
551
+ self.assertEqual(proc.returncode, 1)
552
+ self.assertIn('Could not resolve pointer', proc.stderr)
553
+ self.assertEqual(proc.stdout, '')
554
+
555
+ def test_unresolvable_pointer_in_one_file(self):
556
+ # /b resolves in b.json only; a.json is still reported but b.json
557
+ # is processed
558
+ proc = self._run('/b', 'a.json', 'b.json')
559
+ self.assertEqual(proc.returncode, 1)
560
+ self.assertIn('Could not resolve pointer', proc.stderr)
561
+ self.assertEqual(proc.stdout, '1\n')
562
+
563
+ def test_pointer_file_requires_value(self):
564
+ proc = self._run('-f', '-p', '/a', 'a.json')
565
+ self.assertEqual(proc.returncode, 2)
566
+ self.assertIn('expected one argument', proc.stderr)
567
+
568
+ def test_pointer_and_pointer_file_exclusive(self):
569
+ proc = self._run('-p', '/a', '-f', 'ptr.txt', 'a.json')
570
+ self.assertEqual(proc.returncode, 2)
571
+ self.assertIn('not allowed with', proc.stderr)
572
+
573
+
574
+ class VerboseExceptionsTests(unittest.TestCase):
575
+
576
+ def setUp(self):
577
+ # Save original value and ensure verbose mode is on for each test
578
+ self._original = jsonpointer.VERBOSE_EXCEPTIONS
579
+ jsonpointer.VERBOSE_EXCEPTIONS = True
580
+
581
+ def tearDown(self):
582
+ jsonpointer.VERBOSE_EXCEPTIONS = self._original
583
+
584
+ def test_verbose_exception_includes_doc(self):
585
+ doc = {'foo': 1}
586
+ try:
587
+ resolve_pointer(doc, '/bar')
588
+ self.fail('Expected JsonPointerException')
589
+ except JsonPointerException as e:
590
+ self.assertIn(repr(doc), str(e))
591
+
592
+ def test_non_verbose_exception_excludes_doc(self):
593
+ doc = {'foo': 1}
594
+ jsonpointer.VERBOSE_EXCEPTIONS = False
595
+ try:
596
+ resolve_pointer(doc, '/bar')
597
+ self.fail('Expected JsonPointerException')
598
+ except JsonPointerException as e:
599
+ self.assertNotIn(repr(doc), str(e))
600
+ self.assertIn('bar', str(e))
601
+
602
+
397
603
  def load_tests(loader, tests, ignore):
398
604
  tests.addTests(doctest.DocTestSuite(jsonpointer))
399
605
  return tests
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- import argparse
5
- import json
6
- import sys
7
-
8
- import jsonpointer
9
-
10
- parser = argparse.ArgumentParser(
11
- description='Resolve a JSON pointer on JSON files')
12
-
13
- # Accept pointer as argument or as file
14
- ptr_group = parser.add_mutually_exclusive_group(required=True)
15
-
16
- ptr_group.add_argument('-f', '--pointer-file', type=argparse.FileType('r'),
17
- nargs='?',
18
- help='File containing a JSON pointer expression')
19
-
20
- ptr_group.add_argument('POINTER', type=str, nargs='?',
21
- help='A JSON pointer expression')
22
-
23
- parser.add_argument('FILE', type=argparse.FileType('r'), nargs='+',
24
- help='Files for which the pointer should be resolved')
25
- parser.add_argument('--indent', type=int, default=None,
26
- help='Indent output by n spaces')
27
- parser.add_argument('-v', '--version', action='version',
28
- version='%(prog)s ' + jsonpointer.__version__)
29
-
30
-
31
- def main():
32
- try:
33
- resolve_files()
34
- except KeyboardInterrupt:
35
- sys.exit(1)
36
-
37
-
38
- def parse_pointer(args):
39
- if args.POINTER:
40
- ptr = args.POINTER
41
- elif args.pointer_file:
42
- ptr = args.pointer_file.read().strip()
43
- else:
44
- parser.print_usage()
45
- sys.exit(1)
46
-
47
- return ptr
48
-
49
-
50
- def resolve_files():
51
- """ Resolve a JSON pointer on JSON files """
52
- args = parser.parse_args()
53
-
54
- ptr = parse_pointer(args)
55
-
56
- for f in args.FILE:
57
- doc = json.load(f)
58
- try:
59
- result = jsonpointer.resolve_pointer(doc, ptr)
60
- print(json.dumps(result, indent=args.indent))
61
- except jsonpointer.JsonPointerException as e:
62
- print('Could not resolve pointer: %s' % str(e), file=sys.stderr)
63
-
64
-
65
- if __name__ == "__main__":
66
- main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes