1
2 """The JSONPatch module provides for the alteration of JSON data compliant to RFC6902.
3 """
4 from __future__ import absolute_import
5 from __future__ import print_function
6 from __future__ import division
7
8 import sys
9 import copy
10
11
12 if sys.modules.get('json'):
13 import json as myjson
14 elif sys.modules.get('ujson'):
15 import ujson as myjson
16 else:
17 import json as myjson
18
19
20
21 from jsondata.jsonpointer import JSONPointer
22 from jsondata.jsondataserializer import JSONDataSerializer, MS_OFF
23 from jsondata.jsondata import JSONData
24 from jsondata import V3K, JSONDataPatchError, JSONDataPatchItemError, \
25 C_SHALLOW, C_DEEP, C_REF, \
26 SD_INPUT, SD_OUTPUT
27
28
29 __author__ = 'Arno-Can Uestuensoez'
30 __maintainer__ = 'Arno-Can Uestuensoez'
31 __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
32 __copyright__ = "Copyright (C) 2015-2016 Arno-Can Uestuensoez" \
33 " @Ingenieurbuero Arno-Can Uestuensoez"
34 __version__ = '0.2.21'
35 __uuid__ = '63b597d6-4ada-4880-9f99-f5e0961351fb'
36
37 if V3K:
38 unicode = str
39
40
41 _interactive = False
42
43
44
45 RFC6902_ADD = 1
46 RFC6902_COPY = 2
47 RFC6902_MOVE = 3
48 RFC6902_REMOVE = 4
49 RFC6902_REPLACE = 5
50 RFC6902_TEST = 6
51
52
53
54 op2str = {
55 RFC6902_ADD: "add",
56 RFC6902_COPY: "copy",
57 RFC6902_MOVE: "move",
58 RFC6902_REMOVE: "remove",
59 RFC6902_REPLACE: "replace",
60 RFC6902_TEST: "test"
61 }
62
63
64
65 str2op = {
66 "add": RFC6902_ADD,
67 "copy": RFC6902_COPY,
68 "move": RFC6902_MOVE,
69 "remove": RFC6902_REMOVE,
70 "replace": RFC6902_REPLACE,
71 "test": RFC6902_TEST
72 }
73
74
76 """Converts input into corresponding enumeration.
77 """
78 if type(x) in (
79 int,
80 float, ):
81 return int(x)
82 elif type(x) is (
83 str,
84 unicode, ) and x.isdigit():
85 return int(x)
86 return str2op.get(x, None)
87
88
90 """Record entry for list of patch tasks.
91
92 Attributes:
93 **op**:
94 operations: ::
95
96 add, copy, move, remove, replace, test
97
98 **target**:
99 JSONPointer for the modification target, see RFC6902.
100
101 **value**:
102 Value, either a branch, or a leaf of the JSON data structure.
103
104 **src**:
105 JSONPointer for the modification source, see RFC6902.
106
107 """
108
109 - def __init__(self, op, target, param=None, **kargs):
110 """Create an entry for the patch list.
111
112 Args:
113 **op**:
114 Operation: ::
115
116 add, copy, move, remove, replace, test
117
118 **target**:
119 Target node. ::
120
121 target := (
122 <rfc6901-string>
123 | JSONPointer
124 | <path-items-list>
125 )
126
127 **param**:
128 Specific parameter for the operation.
129
130 +-------+--------------------+
131 | type | operation |
132 +=======+====================+
133 | value | add, replace, test |
134 +-------+--------------------+
135 | src | copy, move |
136 +-------+--------------------+
137 | param | None for 'remove' |
138 +-------+--------------------+
139
140 kargs:
141 **replace**:
142 Replace masked characters in *target* specification. ::
143
144 replace := (
145 True # replaces rfc6901 escape sequences: ~0 and ~1
146 | False # omit unescaping
147 )
148
149 .. note::
150
151 Match operations are proceeded literally, thus the
152 escaped characters should be consistent,
153 see rfc6901, Section 3.
154
155 default := False
156
157 Returns:
158 When successful returns 'True', else returns either 'False', or
159 raises an exception.
160 Success is the complete addition only, thus one failure returns
161 False.
162
163 Raises:
164 JSONDataPatchItemError
165
166 """
167 self.replace = kargs.get('replace', False)
168
169 self.value = None
170 self.src = None
171
172 self.op = getOp(op)
173 self.target = JSONPointer(target, replace=self.replace)
174
175 if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
176 self.value = param
177
178 elif self.op is RFC6902_REMOVE:
179 pass
180
181 elif self.op in (RFC6902_COPY, RFC6902_MOVE):
182 self.src = param
183
184 else:
185 raise JSONDataPatchItemError("Unknown operation.")
186
201
203 """Evaluates the related task for the provided data.
204
205 Args:
206 **jdata**:
207 JSON data the task has to be
208 applied on.
209
210 Returns:
211 Returns a tuple of: ::
212
213 (n,lerr):
214
215 n: number of present active entries
216 lerr: list of failed entries
217
218 Raises:
219 JSONDataPatchError:
220 """
221 return self.apply(jdata)
222
224 """Compares this pointer with x.
225
226 Args:
227 **x**:
228 A valid Pointer.
229
230 Returns:
231 *True* or *False*.
232
233 Raises:
234 JSONPointerError
235 """
236 if x == None:
237 return False
238
239 ret = True
240
241 if type(x) == dict:
242 ret &= self.target == x['path']
243 else:
244 ret &= self.target == x['target']
245
246 if self.op == RFC6902_ADD:
247 ret &= x['op'] in ('add', RFC6902_ADD)
248 ret &= self.value == x['value']
249 elif self.op == RFC6902_REMOVE:
250 ret &= x['op'] in ('remove', RFC6902_REMOVE)
251 elif self.op == RFC6902_REPLACE:
252 ret &= x['op'] in ('replace', RFC6902_REPLACE)
253 ret &= self.value == x['value']
254 elif self.op == RFC6902_MOVE:
255 ret &= x['op'] in ('move', RFC6902_MOVE)
256 ret &= self.src == x['from']
257 elif self.op == RFC6902_COPY:
258 ret &= x['op'] in ('copy', RFC6902_COPY)
259 ret &= self.src == x['from']
260 elif self.op == RFC6902_TEST:
261 ret &= x['op'] in ('test', RFC6902_TEST)
262 ret &= self.value == x['value']
263
264 return ret
265
267 """Support of various mappings.
268
269 #. self[key]
270
271 #. self[i:j:k]
272
273 #. x in self
274
275 #. for x in self
276
277 """
278 if key in (
279 'path',
280 'target', ):
281 return self.target
282 elif key in ('op', ):
283 return self.op
284 elif key in (
285 'value',
286 'param', ):
287 return self.value
288 elif key in (
289 'from',
290 'src', ):
291 return self.src
292
294 """Compares this pointer with x.
295
296 Args:
297 **x**: A valid Pointer.
298
299 Returns:
300 *True* or *False*.
301
302 Raises:
303 JSONPointerError
304 """
305 return not self.__eq__(x)
306
308 if x == None:
309 raise JSONDataPatchError("Missing patch entry/patch")
310 if isinstance(x, JSONPatchItem):
311 ret = JSONPatch()
312 ret.patch.append(x)
313 ret.patch.append(self)
314 return ret
315 elif isinstance(x, JSONPatch):
316 ret = JSONPatch()
317 ret.patch.extend(x.patch)
318 ret.patch.append(self)
319 return ret
320 else:
321 raise JSONDataPatchError("Unknown input" + type(x))
322
324 """Prints the patch string in accordance to RFC6901.
325 """
326 ret = '{"op": "' + unicode(op2str[self.op]) + \
327 '", "path": "' + unicode(self.target) + '"'
328 if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
329 if type(self.value) in (int, float):
330 ret += ', "value": ' + unicode(self.value)
331 elif type(self.value) in (dict, list):
332 ret += ', "value": ' + repr(self.value)
333 else:
334 ret += ', "value": "' + unicode(self.value) + '"'
335
336 elif self.op is RFC6902_REMOVE:
337 pass
338
339 elif self.op in (RFC6902_COPY, RFC6902_MOVE):
340 ret += ', "from": "' + unicode(self.src) + '"'
341 ret += "}"
342 return ret
343
345 """Prints the patch string in accordance to RFC6901.
346 """
347 ret = '{"op": "' + op2str[self.op] + \
348 '", "target": "' + str(self.target)
349 if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
350 if type(self.value) in (int, float):
351 ret += '", "value": ' + str(self.value) + ' }'
352 else:
353 ret += '", "value": "' + str(self.value) + '" }'
354
355 elif self.op is RFC6902_REMOVE:
356 ret += '" }'
357
358 elif self.op in (RFC6902_COPY, RFC6902_MOVE):
359 ret += '", "src": "' + str(self.src) + '" }'
360 return ret
361
362 - def apply(self, jsondata, **kargs):
363 """Applies the present patch list on the provided JSON document.
364
365 Args:
366 **jsondata**:
367 Document to be patched.
368
369 kargs:
370 **replace**:
371 Replace masked characters in *target* specification. ::
372
373 replace := (
374 True # replaces rfc6901 escape sequences: ~0 and ~1
375 | False # omit unescaping
376 )
377
378 .. note::
379
380 Match operations are proceeded literally, thus the
381 escaped characters should be consistent,
382 see rfc6901, Section 3.
383
384 If already decoded e.g. by the constructor, than should be
385 *FALSE*, is not idempotent.
386
387 default := False
388
389 Returns:
390 When successful returns 'True', else raises an exception.
391 Or returns a tuple: ::
392
393 (n,lerr):
394
395 n: number of present active entries
396 lerr: list of failed entries
397
398 Raises:
399 JSONDataPatchError:
400 """
401 replace = kargs.get('replace', self.replace)
402
403 if self.op is RFC6902_ADD:
404 return jsondata.branch_add(
405 self.value, self.target)
406
407 if isinstance(jsondata, JSONDataSerializer):
408 jsondata = jsondata.data
409
410 if self.op is RFC6902_REPLACE:
411 n, b = self.target.get_node_and_key(jsondata)
412 n[b] = self.value
413
414 elif self.op is RFC6902_TEST:
415 n, b = JSONPointer(self.target, replace=replace).get_node_and_key(jsondata)
416
417
418
419
420
421 if isinstance(n, list):
422 return n[int(b)] == self.value
423 elif isinstance(n, dict):
424 return n[unicode(b)] == self.value
425
426 elif self.op is RFC6902_COPY:
427 val = JSONPointer(self.src, replace=replace).get_node_value(jsondata)
428 tn, tc = self.target.get_node_and_key(jsondata)
429 tn[tc] = val
430
431 elif self.op is RFC6902_MOVE:
432 val = JSONPointer(self.src, replace=replace).get_node_value(jsondata)
433 sn, sc = JSONPointer(self.src, replace=replace).get_node_and_key(jsondata)
434 sn.pop(sc)
435 tn, tc = self.target.get_node_and_key(jsondata)
436 if type(tn) is list:
437 if len(tn) <= tc:
438 tn.append(val)
439 else:
440 tn[tc] = val
441 else:
442 tn[tc] = val
443
444 elif self.op is RFC6902_REMOVE:
445 n, b = self.target.get_node_and_key(jsondata)
446 n.pop(b)
447
448 return True
449
451 """Prints the patch string for export in accordance to RFC6901.
452 """
453 ret = '{"op": "' + str(op2str[self.op]) + \
454 '", "path": "' + str(self.target) + '"'
455 if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
456 if type(self.value) in (int, float):
457 ret += ', "value": ' + str(self.value)
458 elif type(self.value) in (dict, list):
459 ret += ', "value": ' + str(self.value)
460 elif type(self.value) is None:
461 ret += ', "value": null'
462 elif type(self.value) is False:
463 ret += ', "value": false'
464 elif type(self.value) is True:
465 ret += ', "value": true'
466 else:
467 ret += ', "value": "' + str(self.value) + '"'
468
469 elif self.op is RFC6902_REMOVE:
470 pass
471
472 elif self.op in (RFC6902_COPY, RFC6902_MOVE):
473 ret += ', "from": "' + str(self.src) + '"'
474 ret += '}'
475 return ret
476
478 """Pretty prints the patch string for export in accordance to RFC6901.
479 """
480 ret = '{"op": "' + str(op2str[self.op]) + \
481 '", "path": "' + str(self.target) + '"'
482 if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
483 if type(self.value) in (int, float):
484 ret += ', "value": ' + str(self.value)
485 elif type(self.value) in (dict, list):
486 ret += ', "value": ' + str(self.value)
487 elif type(self.value) is None:
488 ret += ', "value": null'
489 elif type(self.value) is False:
490 ret += ', "value": false'
491 elif type(self.value) is True:
492 ret += ', "value": true'
493 else:
494 ret += ', "value": "' + str(self.value) + '"'
495
496 elif self.op is RFC6902_REMOVE:
497 pass
498
499 elif self.op in (RFC6902_COPY, RFC6902_MOVE):
500 ret += ', "from": "' + str(self.src) + '"'
501 ret += '}'
502 return ret
503
505 """Adds native patch strings or an unsorted dict for RFC6902.
506
507 Calls parent *JSONPatchItem*.
508
509 """
510
511 - def __init__(self, patchstring, **kargs):
512 """Parse a raw patch string in accordance to RFC6902.
513 """
514 self.replace = kargs.get('replace', False)
515
516 if type(patchstring) in (
517 str,
518 unicode, ):
519 ps = myjson.loads(patchstring)
520 sx = myjson.dumps(ps)
521 if len(sx.replace(" ", "")) != len(patchstring.replace(" ", "")):
522 raise JSONDataPatchItemError(
523 "Repetition is not compliant to RFC6902:" +
524 str(patchstring))
525 elif type(patchstring) is dict:
526 ps = patchstring
527 else:
528 raise JSONDataPatchItemError("Type not supported:" +
529 str(patchstring))
530
531 try:
532 target = ps['path']
533 op = getOp(ps['op'])
534
535 if op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
536 param = ps['value']
537
538 elif op is RFC6902_REMOVE:
539 param = None
540
541 elif op in (RFC6902_COPY, RFC6902_MOVE):
542 param = ps['from']
543 except Exception as e:
544 raise JSONDataPatchItemError(e)
545
546 super(JSONPatchItemRaw, self).__init__(op, target, param, **kargs)
547
548
550 """Filtering capabilities on the entries of patch lists.
551
552 .. warning::
553
554 Not yet implemented.
555
556 """
557
559 """
560 Args:
561 kargs:
562 Filter parameters:
563
564 **common**:
565
566 contain=(True|False): Contain, else equal.
567
568 type=<node-type>: Node is of type.
569
570 **paths**:
571
572 branch=<branch>:
573
574 deep=(): Determines the depth of comparison.
575
576 prefix=<prefix>: Any node of prefix. If prefix is
577 absolute: the only and one, else None.
578 relative: any node prefixed by the path fragment.
579
580 **values**:
581 val=<node-value>: Node ha the value.
582
583
584 Returns:
585 True or False
586
587 Raises:
588 JSONPointerError:
589 """
590 for k, v in kargs:
591 if k == 'prefix':
592 self.prefix = v
593 elif k == 'branch':
594 self.branch = v
595
599
603
604
606 """ Representation of a JSONPatch task list for RFC6902.
607
608 Contains the defined methods from standards:
609
610 * add
611 * remove
612 * replace
613 * move
614 * copy
615 * test
616
617 Attributes:
618 **patch**:
619 List of patch items.
620
621 """
622
624 """List of patch tasks.
625
626 Args:
627 **p**:
628 Patch list. ::
629
630 p := (
631 JSONPatch
632 | <list-of-patch-items>
633 )
634
635 kargs:
636 **replace**:
637 Replace masked characters in *target* specification. ::
638
639 replace := (
640 True # replaces rfc6901 escape sequences: ~0 and ~1
641 | False # omit unescaping
642 )
643
644 .. note::
645
646 Match operations are proceeded literally, thus the
647 escaped characters should be consistent,
648 see rfc6901, Section 3.
649
650 default := False
651
652 Returns:
653 self.
654
655 Raises:
656 pass-through
657 """
658 assert isinstance(p, list) or p == None
659
660 self.replace = kargs.get('replace', False)
661
662 if p:
663 self.patch = p
664 else:
665 self.patch = []
666
667 self.deep = False
668 """Defines copy operations, True:=deep, False:=swallow"""
669
671 """Creates and adds patch job to the task queue.
672
673 Args:
674 **x**:
675 Extension of pathc job, eithe a *JSONPatch*,
676 or a *JSONPatchItem*.
677
678 Returns:
679 Returns a patch job, or raises Exception.
680
681 Raises:
682 JSONDataPatchError
683
684 """
685 if x == None:
686 raise JSONDataPatchError("Missing patch entry/patch")
687 if isinstance(x, JSONPatchItem):
688 ret = JSONPatch(self.patch)
689 ret.patch.append(x)
690 return ret
691 elif isinstance(x, JSONPatch):
692 ret = JSONPatch(self.patch)
693 ret.patch.extend(x.patch)
694 return ret
695 else:
696 raise JSONDataPatchError("Unknown input" + type(x))
697
699 """Evaluates the related task for the provided index.
700
701 Args:
702 **x**:
703 Task index.
704
705 **jdata**:
706 JSON data the task has to be
707 applied on.
708
709 Returns:
710 Returns a tuple of: ::
711
712 (n, lerr):
713
714 n: number of present active entries
715 lerr: list of failed entries
716
717 Raises:
718 JSONDataPatchError:
719 """
720 if x is None:
721 return self.apply(jdata)
722 if self.patch[x](jdata):
723 return 1, []
724 return 1, [0]
725
727 """Compares this pointer with x.
728
729 Args:
730 **x**:
731 A valid Pointer.
732
733 Returns:
734 *True* or *False*
735
736 Raises:
737 JSONPointerError
738 """
739 if x == None:
740 return False
741
742 match = len(self.patch)
743 if match != len(x):
744 return False
745
746 if isinstance(x, JSONPatch):
747 return self.patch == x.patch
748 elif isinstance(x, list):
749 ret = True
750 for i in range(match):
751 for k in x[i].keys():
752 if k == 'op':
753 ret &= str(x[i][k]) == op2str[self.patch[i][k]]
754 else:
755 ret &= str(x[i][k]) == str(self.patch[i][k])
756 return ret
757 else:
758 raise JSONDataPatchError("Type requires JSONPatch or list, got" + str(type(x)))
759
761 """Support of slices, for 'iterator' refer to self.__iter__.
762
763 #. self[key]
764
765 #. self[i:j:k]
766
767 #. x in self
768
769 #. for x in self
770
771 """
772 return self.patch[key]
773
775 """Adds patch jobs to the task queue in place.
776 """
777 if x == None:
778 raise JSONDataPatchError("Missing patch entry/patch")
779 if isinstance(x, JSONPatchItem):
780 self.patch.append(x)
781 elif isinstance(x, JSONPatch):
782 self.patch.extend(x.patch)
783 else:
784 raise JSONDataPatchError("Unknown input" + type(x))
785 return self
786
788 """Removes the patch job from the task queue in place.
789
790 Removes one of the following type(x) variants:
791
792 *int*:
793 The patch job with given index.
794
795 *JSONPatchItem*:
796 The first matching entry from
797 the task queue.
798
799 Args:
800 **x**:
801 Item to be removed. ::
802
803 x := (
804 int
805 | JSONPatchItem
806 )
807
808 Returns:
809 Returns resulting list without x.
810
811 Raises:
812 JSONDataPatchError:
813 """
814 if type(x) is int:
815 self.patch.pop(x)
816 else:
817 self.patch.remove(x)
818 return self
819
821 """Provides an iterator foreseen for large amounts of in-memory patches.
822 """
823 return iter(self.patch)
824
826 """The number of outstanding patches.
827 """
828 return len(self.patch)
829
831 """Compares this pointer with x.
832
833 Args:
834 **x**:
835 A valid Pointer.
836
837 Returns:
838 *True* or *False*
839
840 Raises:
841 JSONPointerError
842 """
843 return not self.__eq__(x)
844
846 """Adds a copy of xto the task queue.
847 """
848 if x == None:
849 raise JSONDataPatchError("Missing patch entry/patch")
850 if isinstance(x, JSONPatchItem):
851 ret = JSONPatch(x)
852 ret.patch.append(self.patch)
853 return ret
854 elif isinstance(x, JSONPatch):
855 ret = JSONPatch(x)
856 ret.patch.extend(self.patch)
857 return ret
858 else:
859 raise JSONDataPatchError("Unknown input" + type(x))
860
862 """Prints the representation format of a JSON patch list.
863 """
864 ret = "["
865 if self.patch:
866 if len(self.patch) > 1:
867 for p in self.patch[:-1]:
868 ret += repr(p) + ", "
869 ret += repr(self.patch[-1])
870 ret += "]"
871 return unicode(ret)
872
874 """Prints the display format.
875 """
876 ret = "[\n"
877 if self.patch:
878 if len(self.patch) > 1:
879 for p in self.patch[:-1]:
880 ret += " " + repr(p) + ",\n"
881 ret += " " + repr(self.patch[-1]) + "\n"
882 ret += "]"
883 return str(ret)
884
886 """Removes the patch job from the task queue.
887
888 Removes one of the following type(x) variants:
889
890 *int*:
891 The patch job with given index.
892
893 *JSONPatchItem*:
894 The first matching entry from
895 the task queue.
896
897 Args:
898 **x**:
899 Item to be removed. ::
900
901 x := (
902 int
903 | JSONPatchItem
904 )
905
906 Returns:
907 Returns resulting list without x.
908
909 Raises:
910 JSONDataPatchError:
911 """
912 ret = JSONPatch()
913 if self.deep:
914 ret.patch = self.patch[:]
915 else:
916 ret.patch = self.patch
917
918 if type(x) is int:
919 ret.patch.pop(x)
920 else:
921 ret.patch.remove(x)
922 return ret
923
924 - def apply(self, jsondata, **kargs):
925 """Applies the JSONPatch task.
926
927 Args:
928 **jsondata**:
929 JSON data the joblist has to be applied on.
930
931 Returns:
932 Returns a tuple of: ::
933
934 (n, lerr):
935
936 n: number of present active entries
937 lerr: list of failed entries
938
939 Raises:
940 JSONDataPatchError:
941 """
942 status = []
943 for p in self.patch:
944 if not p.apply(jsondata, **kargs):
945
946 status.append(self.patch.index(p))
947 return len(self.patch), status
948
950 """Gets the reference to a single patch item.
951
952 Args:
953 **x**:
954 Requested item. ::
955
956 x := (
957 int
958 | JSONPatchItem
959 )
960
961 int: index of patch item
962 JSONPatchItem: the reference to the patch item
963
964 Returns:
965 The selected patch item.
966
967 Raises:
968 None
969 """
970 if x == None:
971 return
972 if type(x) is int:
973 return self.patch[x]
974 return self.patch[self.patch.index(x)]
975
977 """Gets a list of references of patch items.
978
979 Args:
980 args:
981 Requested items. ::
982
983 *args := (
984 <item>
985 | <item-list>
986 | None
987 )
988 item-list := <item>[, <item-list>]
989 item := (
990 int
991 | JSONPatchItem
992 )
993 None := "all items of the current patch list"
994
995 int: index of patch item
996 JSONPatchItem: the reference to the patch item
997
998 kargs:
999 **idxlist**:
1000 Print with index: ::
1001
1002 idxlist := (
1003 True # format: [{<index>: <JSONPatchItem>}]
1004 | False # format: [<JSONPatchItem>]
1005 )
1006
1007 **copydata**:
1008 Creates a copy of each resulting item. ::
1009
1010 copydata := (C_DEEP | C_SHALLOW | C_REF)
1011
1012 default := C_REF # no copy
1013
1014 Returns:
1015 A list of the selected patch items.
1016
1017 Raises:
1018 None
1019 """
1020 ret = []
1021 _copy = kargs.get('copydata', C_REF)
1022
1023 if args and args[0] == None:
1024 args = self.patch
1025
1026 for x in args:
1027 if type(x) is int:
1028 _c = self.patch[x]
1029 else:
1030 x = self.patch.index(x)
1031 _c = self.patch[x]
1032
1033 if _copy == C_SHALLOW:
1034 _c = copy.copy(_c)
1035 elif _copy == C_DEEP:
1036 _c = copy.deepcopy(_c)
1037
1038 if kargs.get('idxlist'):
1039 ret.append({x: _c})
1040 else:
1041 ret.append(_c)
1042
1043 return ret
1044
1045 - def gettree(self, *args, **kargs):
1046 """Gets the resulting logical JSON data structure
1047 constructed from the patch items of the current set.
1048
1049 Args:
1050 args:
1051 Requested items. ::
1052
1053 *args := (
1054 <item>
1055 | <item-list>
1056 | None
1057 )
1058 item-list := <item>[, <item-list>]
1059 item := (
1060 int
1061 | JSONPatchItem
1062 )
1063 None := "all items of the current patch list"
1064
1065 int: index of patch item
1066 JSONPatchItem: the reference to the patch item
1067
1068 kargs:
1069 **data**:
1070 An optional JSON data structure, when provided
1071 the actual data as selected by the patch list
1072 is returned. Else the paths only.
1073
1074 default := None
1075
1076 **scope**:
1077 Defines the source scope of the data structure. ::
1078
1079 scope := (
1080 "in" # input data, e.g. source for "copy"
1081 | "out" # output data, e.g. target for "copy
1082 )
1083
1084 default := "out"
1085
1086 Returns:
1087 The combined list of the selected patch items contained
1088 in an object JSONData.
1089
1090 Raises:
1091 None
1092 """
1093 _data = kargs.get('data', None)
1094 _scope = kargs.get('scope', 'out')
1095 if _scope == 'in':
1096 _scope = True
1097 else:
1098 _scope = False
1099
1100 _pl = self.getpatchitems(*args)
1101
1102 ret = JSONData('')
1103
1104 for x in _pl:
1105 if _scope == SD_INPUT:
1106 if x['op'] in (RFC6902_ADD,):
1107 ret.branch_add('add', x['path'])
1108 elif x['op'] in (RFC6902_COPY,):
1109 ret.branch_add('copy', x['from'])
1110 elif x['op'] in (RFC6902_MOVE,):
1111 ret.branch_add('move', x['from'])
1112 elif x['op'] in (RFC6902_REMOVE,):
1113 ret.branch_add('remove', x['path'])
1114 elif x['op'] in (RFC6902_REPLACE,):
1115 ret.branch_add('replace', x['path'])
1116 elif x['op'] in (RFC6902_TEST,):
1117 ret.branch_add('test', x['path'])
1118
1119 elif _scope == SD_OUTPUT:
1120 if x['op'] in (RFC6902_ADD,):
1121 ret.branch_add('add', x['path'])
1122 elif x['op'] in (RFC6902_COPY,):
1123 ret.branch_add('copy', x['path'])
1124 elif x['op'] in (RFC6902_MOVE,):
1125 ret.branch_add('move', x['path'])
1126 elif x['op'] in (RFC6902_REMOVE,):
1127 ret.branch_add('remove', x['path'])
1128 elif x['op'] in (RFC6902_REPLACE,):
1129 ret.branch_add('replace', x['path'])
1130 elif x['op'] in (RFC6902_TEST,):
1131 ret.branch_add('test', x['path'])
1132
1133 else:
1134 if x['op'] in (RFC6902_ADD,):
1135 ret.branch_add('add', x['path'])
1136 elif x['op'] in (RFC6902_COPY,):
1137 ret.branch_add('copy', x['from'])
1138 ret.branch_add('copy', x['path'])
1139 elif x['op'] in (RFC6902_MOVE,):
1140 ret.branch_add('move', x['from'])
1141 ret.branch_add('move', x['path'])
1142 elif x['op'] in (RFC6902_REMOVE,):
1143 ret.branch_add('remove', x['path'])
1144 elif x['op'] in (RFC6902_REPLACE,):
1145 ret.branch_add('replace', x['path'])
1146 elif x['op'] in (RFC6902_TEST,):
1147 ret.branch_add('test', x['path'])
1148
1149 return ret
1150
1152 """Exports the current task list.
1153
1154 Args:
1155 **patchfile**:
1156 JSON patch for export.
1157
1158 **schema**:
1159 JSON-Schema for validation of the patch list.
1160
1161 kargs:
1162 **validator**: [default, draft3, off, ]
1163 Sets schema validator for the data file.
1164 The values are: ::
1165
1166 default = validate
1167 draft3 = Draft3Validator
1168 off = None
1169
1170 default:= validate
1171
1172 **pretty**:
1173 If True exports as tree format,
1174 else all as one line.
1175
1176 Returns:
1177 When successful returns 'True', else raises an exception.
1178
1179 Raises:
1180 JSONDataPatchError:
1181
1182 """
1183 pretty = kargs.get('pretty', False)
1184
1185 if not pretty:
1186 try:
1187 with open(patchfile, 'w') as fp:
1188 fp.writelines(self.repr_export())
1189 except Exception as e:
1190 raise JSONDataPatchError("open-" + str(e), "data.dump",
1191 str(patchfile))
1192
1193 else:
1194 try:
1195 with open(patchfile, 'w') as fp:
1196 fp.writelines(str(self.str_export()))
1197 except Exception as e:
1198 raise JSONDataPatchError("open-" + str(e), "data.dump",
1199 str(patchfile))
1200
1201 return True
1202
1203 - def patch_import(self, patchfile, schemafile=None, **kargs):
1204 """Imports a task list.
1205
1206 Args:
1207 **patchfile**:
1208 JSON patch filename containing the list of patch operations.
1209
1210 **schemafile**:
1211 JSON-Schema filename for validation of the patch list.
1212
1213 kargs:
1214 **replace**:
1215 Replace masked characters in *target* specification. ::
1216
1217 replace := (
1218 True # replaces rfc6901 escape sequences: ~0 and ~1
1219 | False # omit unescaping
1220 )
1221
1222 .. note::
1223
1224 Match operations are proceeded literally, thus the
1225 escaped characters should be consistent,
1226 see rfc6901, Section 3.
1227
1228 default := False
1229
1230 **validator**:
1231 Sets schema validator for the data file.
1232 Curren release relies on *jsonschema*, which
1233 supports at the time of writing draft-03 and
1234 draft-04.
1235
1236 The values are: ::
1237
1238 validator := (
1239 MS_DRAFT3 | 'draft3'
1240 | MS_DRAFT4 | 'draft4'
1241 | MS_ON | 'on'
1242 | MS_OFF | 'off'
1243 | MODE_SCHEMA_DEFAULT | 'default'
1244 )
1245
1246 default:= MS_OFF
1247
1248 Returns:
1249 When successful returns 'True', else raises an exception.
1250
1251 Raises:
1252 JSONDataPatchError:
1253
1254 """
1255 replace = kargs.get('replace', self.replace)
1256 validator = kargs.get('validator', MS_OFF)
1257
1258 patchdata = JSONDataSerializer(
1259 [],
1260 datafile=patchfile,
1261 schemafile=schemafile,
1262 validator=MS_OFF,
1263 )
1264
1265 for pi in patchdata.data:
1266 self += JSONPatchItemRaw(pi, replace=replace)
1267 return True
1268
1270 """Prints the export representation format of a JSON patch list.
1271 """
1272 ret = "["
1273 if self.patch:
1274 if len(self.patch) > 1:
1275 for p in self.patch[:-1]:
1276 ret += p.repr_export() + ", "
1277 ret += self.patch[-1].repr_export()
1278 ret += "]"
1279 return ret
1280
1282 """Pretty prints the export representation format of a JSON patch list.
1283 """
1284 ret = "[\n"
1285 if self.patch:
1286 if len(self.patch) > 1:
1287 for p in self.patch[:-1]:
1288 ret += " " + p.str_export() + ",\n"
1289 ret += " " + self.patch[-1].str_export() + "\n"
1290 ret += "]"
1291 return str(ret)
1292