jsonpointer 3.3__tar.gz → 3.4__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.3
3
+ Version: 3.4
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonpointer
3
- Version: 3.3
3
+ Version: 3.4
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.3'
35
+ __version__ = '3.4'
36
36
  __website__ = 'https://github.com/stefankoegl/python-json-pointer'
37
37
  __license__ = 'Modified BSD License'
38
38
 
@@ -48,12 +48,17 @@ _nothing = object()
48
48
  VERBOSE_EXCEPTIONS = True
49
49
 
50
50
 
51
- def set_pointer(doc, pointer, value, inplace=True):
51
+ def set_pointer(doc, pointer, value, inplace=True, create=False):
52
52
  """Resolves a pointer against doc and sets the value of the target within doc.
53
53
 
54
54
  With inplace set to true, doc is modified as long as pointer is not the
55
55
  root.
56
56
 
57
+ With create set to true, missing intermediate members are created as
58
+ dicts instead of raising a JsonPointerException. A missing intermediate
59
+ array element can be created by referencing the end of the array, either
60
+ with "-" or with an index equal to the length of the array.
61
+
57
62
  >>> obj = {'foo': {'anArray': [ {'prop': 44}], 'another prop': {'baz': 'A string' }}}
58
63
 
59
64
  >>> set_pointer(obj, '/foo/anArray/0/prop', 55) == \
@@ -68,10 +73,14 @@ def set_pointer(doc, pointer, value, inplace=True):
68
73
  >>> set_pointer(obj, '/foo/a%20b', 'x') == \
69
74
  {'foo': {'a%20b': 'x' }}
70
75
  True
76
+
77
+ >>> set_pointer({}, '/cat/name', 'whiskers', create=True) == \
78
+ {'cat': {'name': 'whiskers'}}
79
+ True
71
80
  """
72
81
 
73
82
  pointer = JsonPointer(pointer)
74
- return pointer.set(doc, value, inplace)
83
+ return pointer.set(doc, value, inplace, create)
75
84
 
76
85
 
77
86
  def resolve_pointer(doc, pointer, default=_nothing):
@@ -200,8 +209,11 @@ class JsonPointer:
200
209
 
201
210
  get = resolve
202
211
 
203
- def set(self, doc, value, inplace=True):
204
- """Resolve the pointer against the doc and replace the target with value."""
212
+ def set(self, doc, value, inplace=True, create=False):
213
+ """Resolve the pointer against the doc and replace the target with value.
214
+
215
+ With create set to true, missing intermediate members are created as
216
+ dicts (see set_pointer)."""
205
217
 
206
218
  if len(self.parts) == 0:
207
219
  if inplace:
@@ -211,20 +223,67 @@ class JsonPointer:
211
223
  if not inplace:
212
224
  doc = copy.deepcopy(doc)
213
225
 
214
- (parent, part) = self.to_last(doc)
226
+ if create:
227
+ parent = doc
228
+ for i, part in enumerate(self.parts[:-1]):
229
+ part = self._get_create_part(parent, part)
230
+ if self._is_missing(parent, part):
231
+ # Everything below this point is new, so build the
232
+ # missing structure detached and attach it in one step.
233
+ # This leaves doc unchanged if anything goes wrong.
234
+ child = value
235
+ for missing in reversed(self.parts[i + 1:]):
236
+ child = {missing: child}
237
+ self._set_child(parent, part, child)
238
+ return doc
239
+ parent = self.walk(parent, part)
240
+ part = self._get_create_part(parent, self.parts[-1])
241
+ else:
242
+ (parent, part) = self.to_last(doc)
243
+
244
+ self._set_child(parent, part, value)
245
+ return doc
246
+
247
+ @classmethod
248
+ def _get_create_part(cls, doc, part):
249
+ """Like get_part, but maps an index just past the end of a sequence
250
+ to "-" so that it is appended"""
251
+
252
+ part = cls.get_part(doc, part)
253
+ if isinstance(doc, Sequence) and part == len(doc):
254
+ return '-'
255
+ return part
256
+
257
+ @staticmethod
258
+ def _is_missing(doc, part):
259
+ """Returns True if part does not exist in doc but could be added"""
260
+
261
+ if isinstance(doc, Mapping):
262
+ return part not in doc
263
+
264
+ if isinstance(doc, Sequence) and not isinstance(doc, str):
265
+ return part == '-'
266
+
267
+ return False
268
+
269
+ @staticmethod
270
+ def _set_child(parent, part, value):
271
+ """Sets parent[part] to value, appending for the end of a sequence"""
215
272
 
216
273
  if isinstance(parent, str):
217
274
  raise JsonPointerException("Cannot set value in a string")
218
275
  elif isinstance(parent, Sequence) and part == '-':
219
- parent.append(value)
276
+ try:
277
+ parent.append(value)
278
+ except AttributeError as e:
279
+ # immutable sequences, such as tuples, can't be appended to
280
+ raise JsonPointerException("Invalid assignment target: %s" % (e,))
220
281
  else:
221
282
  try:
222
283
  parent[part] = value
223
284
  except (TypeError, IndexError) as e:
224
285
  raise JsonPointerException("Invalid assignment target: %s" % (e,))
225
286
 
226
- return doc
227
-
228
287
  @classmethod
229
288
  def get_part(cls, doc, part):
230
289
  """Returns the next step in the correct type"""
@@ -386,6 +386,69 @@ class SetTests(unittest.TestCase):
386
386
 
387
387
  self.assertRaises(JsonPointerException, set_pointer, doc, "", 9)
388
388
 
389
+ def test_set_create(self):
390
+ doc = {}
391
+ set_pointer(doc, "/cat/name", "whiskers", create=True)
392
+ self.assertEqual(doc, {"cat": {"name": "whiskers"}})
393
+
394
+ doc = {"cities": {"NewYork": "0"}}
395
+ set_pointer(doc, "/cities/LosAngeles/population", "4mln", create=True)
396
+ self.assertEqual(doc, {"cities": {
397
+ "NewYork": "0",
398
+ "LosAngeles": {"population": "4mln"},
399
+ }})
400
+
401
+ # existing members are kept
402
+ set_pointer(doc, "/cities/LosAngeles/area", 1302, create=True)
403
+ self.assertEqual(doc["cities"]["LosAngeles"],
404
+ {"population": "4mln", "area": 1302})
405
+
406
+ def test_set_create_not_inplace(self):
407
+ doc = {"a": {}}
408
+ newdoc = set_pointer(doc, "/a/b/c", 1, inplace=False, create=True)
409
+ self.assertEqual(newdoc, {"a": {"b": {"c": 1}}})
410
+ self.assertEqual(doc, {"a": {}})
411
+
412
+ def test_set_create_list(self):
413
+ doc = {"foo": []}
414
+ set_pointer(doc, "/foo/-/bar", 1, create=True)
415
+ self.assertEqual(doc, {"foo": [{"bar": 1}]})
416
+
417
+ set_pointer(doc, "/foo/1/bar", 2, create=True)
418
+ self.assertEqual(doc, {"foo": [{"bar": 1}, {"bar": 2}]})
419
+
420
+ set_pointer(doc, "/foo/0/baz", 3, create=True)
421
+ self.assertEqual(doc, {"foo": [{"bar": 1, "baz": 3}, {"bar": 2}]})
422
+
423
+ set_pointer(doc, "/foo/2", 4, create=True)
424
+ self.assertEqual(doc, {"foo": [{"bar": 1, "baz": 3}, {"bar": 2}, 4]})
425
+
426
+ def test_set_create_invalid(self):
427
+ doc = {"foo": [1], "s": "str"}
428
+ # indices past the end of a list can't be created
429
+ self.assertRaises(JsonPointerException, set_pointer, doc,
430
+ "/foo/5/bar", 1, create=True)
431
+ # nothing can be created inside a string
432
+ self.assertRaises(JsonPointerException, set_pointer, doc,
433
+ "/s/bar/baz", 1, create=True)
434
+ self.assertEqual(doc, {"foo": [1], "s": "str"})
435
+
436
+ def test_set_create_immutable_sequence(self):
437
+ doc = {"items": ()}
438
+ self.assertRaises(JsonPointerException, set_pointer, doc,
439
+ "/items/0/leaf", 7, create=True)
440
+ self.assertRaises(JsonPointerException, set_pointer, doc,
441
+ "/items/0", 7, create=True)
442
+ self.assertRaises(JsonPointerException, set_pointer, doc,
443
+ "/items/-", 7)
444
+ self.assertEqual(doc, {"items": ()})
445
+
446
+ def test_set_create_off_by_default(self):
447
+ doc = {}
448
+ self.assertRaises(JsonPointerException, set_pointer, doc,
449
+ "/cat/name", "whiskers")
450
+ self.assertEqual(doc, {})
451
+
389
452
 
390
453
  class AltTypesTests(unittest.TestCase):
391
454
  class Node(object):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes