1
2 """Core features for the processing of JSON based structures of in-memory data.
3 Supports RFC4627 and RCF7159, addresses of in-memory-nodes and RFC6901,
4 relative pointer-draft.
5 """
6 from __future__ import absolute_import
7 from __future__ import print_function
8 from __future__ import division
9
10 import os
11 import sys
12 import re
13
14 import copy
15
16
17
18 if sys.modules.get('ujson'):
19 import ujson as myjson
20 else:
21 import json as myjson
22
23
24
25 import jsonschema
26 from jsonschema import ValidationError as JSONDataValidationError
27 from jsonschema import SchemaError as JSONDataSchemaError
28
29 from jsondata import V3K, ISSTR
30
31 from jsondata import JSONDataParameterError, JSONDataError, \
32 JSONDataValueError, JSONDataIndexError, JSONDataKeyError, \
33 JSONDataSourceFileError, JSONDataTargetFileError, \
34 JSONDataNodeTypeError, JSONPointerError, JSONPointerTypeError, \
35 JSONDataPathError, \
36 MJ_RFC4627, MJ_RFC7159, MJ_RFC6901, \
37 MJ_RFC6902, MS_OFF, MS_DRAFT3, MS_DRAFT4, \
38 C_REF, C_DEEP, C_SHALLOW, \
39 C_DEFAULT, \
40 PJ_TREE, PJ_FLAT, PJ_PYTREE, PJ_PYFLAT, PJ_REPR, PJ_STR, \
41 JSYN_NATIVE, JSYN_PYTHON, \
42 validator2ms, copy2c, mode2mj, \
43 B_AND, B_OR, B_XOR
44
45 __author__ = 'Arno-Can Uestuensoez'
46 __maintainer__ = 'Arno-Can Uestuensoez'
47 __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
48 __copyright__ = "Copyright (C) 2015-2016 Arno-Can Uestuensoez" \
49 "@Ingenieurbuero Arno-Can Uestuensoez"
50 __version__ = '0.2.21'
51 __uuid__ = '63b597d6-4ada-4880-9f99-f5e0961351fb'
52
53 if V3K:
54 unicode = str
55
56
58 """A wrapper for a 'list' representing a path pointer
59 at the method interfaces. Required due to possible
60 ambiguity with the other type of in-memory node.
61 """
62 pass
63
64
66 """ Representation of a JSON based object data tree.
67
68 The common node address parameters are defined as: ::
69
70 sourcenode := <commonnode>|<indata> # depends on the interface
71
72 targetnode := <innode> # target within the represented
73 # JSON structure
74
75 commonnode := (anydata | indata)
76
77 anydata := ( # any data hook - within
78 # self.data or external
79 JSONData # - adds to the contained data
80 # of the reference
81 | <json-array-list> # - adds to JSON array
82 | <json-object-dict> # - adds to JSON object
83 )
84
85 indata := ( # within self.data
86 JSONPointer # - adds to the node within
87 # self.data [RFC6901]_
88 | <rfc6901-string> # - pointer string within self.data [RFC6901]_
89 | <relative-pointer-string>) # - relative pointer within self.data [RELPOINTER]_
90 )
91
92 **REMARK**:
93 The RFC7159 permits any JSON type as a node, while the RFC4627
94 permits array and object as node reference only.
95 """
96
98 """Creates and validates a new object from the provided JSON data.
99 Arbitrary additional JSON data could be added as branches.
100
101 Args:
102 **jdata**:
103 The initial data of current instance. The accepted formats
104 are in-memory representation of JSON data compatible with
105 the standard library *json* [json]_. The permitted input
106 types vary in accordance to the selected operations mode
107 of either *RFC4627* or *RFC7159*. ::
108
109 type(jdata) := (
110 list, # json-array
111 | dict, # json-object
112 | JSONData # copy constructor
113 | int # RFC7159 only
114 | float # RFC7159 only
115 | unicode # RFC7159 only
116 )
117
118 .. note::
119
120 Further branches could be added to json-objects,
121 and json-arrays only, while values in RFC7159 and RFC8259
122 mode permit replacement only. Objects support string indexes,
123 arrays integer indexes.
124
125 kargs:
126 For the complete set of call parameters refer to
127 the method **JSONData.setkargs()**.
128
129 **mode**:
130 The mode of JSON processing: ::
131
132 mode := (
133 MJ_RFC4627
134 | MJ_RFC7493 # currently not supported, mapped to RFC7159
135 | MJ_RFC7159
136 | MJ_RFC8259
137 | MJ_ECMA404 # same as RFC8259
138 )
139
140 default := MJ_RFC7159
141
142 **schema**:
143 A valid in-memory JSONschema.
144
145 default:= None
146
147 **validator**:
148 Sets schema validator for the data file.
149 Curren release relies on *jsonschema*, which
150 supports at the time of writing draft-03 and
151 draft-04.
152
153 The values are: ::
154
155 validator := (
156 MS_DRAFT3 | 'draft3'
157 | MS_DRAFT4 | 'draft4'
158 | MS_ON | 'on'
159 | MS_OFF | 'off'
160 | MODE_SCHEMA_DEFAULT | 'default'
161 )
162
163 default:= MS_OFF
164
165 Returns:
166
167 Results in an initialized object.
168
169 Raises:
170
171 NameError
172
173 JSONDataValueError
174
175 jsonschema.ValidationError
176
177 jsonschema.SchemaError
178
179 """
180
181
182 self.schemafile = None
183
184
185 self.mode_json = MJ_RFC7159
186 self.mode_schema = MS_DRAFT4
187 self.mode_pointer = MJ_RFC6901
188 self.mode_patch = MJ_RFC6902
189
190 self.saveContext = True
191
192 self.branch = None
193 self.data = None
194 self.schema = None
195 self.indent = 4
196 self.sort_keys = False
197 self.validator = MS_OFF
198
199 self.jsonsyn = JSYN_NATIVE
200
201 self.op_depth = 0
202 self.op_ignore = []
203 self.op_use = []
204 self.state_pre = []
205
206 self.op_cp_pol = kargs.get('copy', C_DEFAULT)
207
208 if __debug__:
209 self.debug = False
210 self.verbose = False
211
212
213 self.setkargs(**kargs)
214
215
216
217
218 if type(jdata) in (list, dict, ):
219 if self.op_cp_pol == C_REF:
220 self.data = jdata
221 elif self.op_cp_pol == C_SHALLOW:
222 self.data = copy.copy(jdata)
223 elif self.op_cp_pol == C_DEEP:
224 self.data = copy.deepcopy(jdata)
225
226 elif isinstance(jdata, JSONData):
227 self.data = copy.deepcopy(jdata.data)
228 self.schema = jdata.schema
229 self.schemafile = jdata.schemafile
230 self.mode_json = jdata.mode_json
231 self.mode_schema = jdata.mode_schema
232 self.mode_pointer = jdata.mode_pointer
233 self.mode_patch = jdata.mode_patch
234 self.saveContext = jdata.saveContext
235 self.indent = jdata.indent
236 self.sort_keys = jdata.sort_keys
237 self.validator = jdata.validator
238 self.op_depth = jdata.op_depth
239 self.op_ignore = jdata.op_ignore[:]
240 self.op_use = jdata.op_use[:]
241 self.state_pre = jdata.state_pre[:]
242
243 try:
244 if self.op_cp_pol == C_REF:
245 self.data = jdata()
246 elif self.op_cp_pol == C_SHALLOW:
247 self.data = copy.copy(jdata())
248 elif self.op_cp_pol == C_DEEP:
249 self.data = copy.deepcopy(jdata())
250 except:
251 raise JSONDataError("Non-valid JSON data:" +
252 str(jdata))
253
254 else:
255 if self.mode_json == MJ_RFC4627:
256 raise JSONDataError(
257 "Mode RFC4627 - Non-valid JSON data, requires object or array: %s - %s"
258 % (str(type(jdata)), str(jdata)))
259
260 try:
261 if self.op_cp_pol == C_REF:
262 self.data = jdata
263 elif self.op_cp_pol == C_SHALLOW:
264 self.data = copy.copy(jdata)
265 elif self.op_cp_pol == C_DEEP:
266 self.data = copy.deepcopy(jdata)
267
268 except:
269 raise JSONDataError(
270 "Non-valid JSON data:" + str(jdata))
271
272 if __debug__:
273 if self.debug > 2:
274 sys.stderr.write("DBG:JSON= " + str(
275 myjson.__name__) + " / " + str(myjson.__version__)
276 + "\nDBG:self.data= #[" + str(self.data) + "]#"
277 + "\nDBG:self.schema= #[" + str(self.schema) + "]#\n"
278 )
279 elif self.verbose:
280 print("VERB:JSON= " + str(myjson.__name__)
281 + " / " + str(myjson.__version__))
282
283
284 if not self.schema and self.validator != MS_OFF:
285 raise JSONDataParameterError("value", "schema", str(self.schema))
286
287
288 if self.validator != MS_OFF:
289 self.validate(self.data, self.schema, self.validator)
290
292 """Sets key arguments.
293
294 Args:
295
296 kargs:
297
298 **copydata**:
299 Controls the assignment policy for JSON data.
300 The schema is copied by reference only, once
301 read. ::
302
303 copydata := (
304 C_REF # by reference
305 | C_DEEP # by copy.deepcopy()
306 | C_SHALLOW # by copy.copy()
307 )
308
309 **debug**:
310 Displays extended state data for developers.
311 Requires __debug__==True.
312
313 **depth**:
314 Sets the default behavior for the operators.
315 Controls, whether the hook only or the complete
316 branch is processed in depth node-by-node. ::
317
318 0: the hook of the branch
319 #n: the level of the branch as integer, the
320 remaining sub-branch is treated by it's hook
321 -1: the complete branch
322
323 default:= 0
324
325 **indent_str**:
326 Defied the indentation of 'str'.
327
328 default:= 4
329
330 **jsonsyntax**:
331 The display syntax for JSON data with
332 *__str__*. ::
333
334 jsonsyntax := (
335 JSYN_NATIVE | 'json'
336 | JSYN_PYTHON | 'python'
337 )
338
339 JSYN_NATIVE: Native standards syntax.
340 JSYN_PYTHON: Python in-memory syntax.
341
342 **mode**:
343 The mode of JSON processing: ::
344
345 mode := (
346 MJ_RFC4627 | 'rfc4627'
347 | MJ_RFC7493 | 'rfc7493' # currently not supported, mapped to RFC7159
348 | MJ_RFC7159 | 'rfc7159'
349 | MJ_RFC8259 | 'rfc8259'
350 | MJ_ECMA404 | 'ecma404' # same as RFC8259
351 )
352
353 default := MJ_RFC7159
354
355 **rtype**:
356 Sets the data type of the returned result. ::
357
358 rtype := (
359 data # returns the data record only - self.data
360 | jdata # returns an object of class JSONData
361 | obj # returns an object of own class
362 )
363
364 default := obj
365
366 **saveContext**:
367 Saves and restores the defined context
368 parameters when entering a context call.
369
370 default:= True
371
372 **schema**:
373 A valid in-memory JSONschema.
374
375 default:= None
376
377 **sortstr**:
378 Sort display of 'str()' by key. ::
379
380 sortstr := (True | False)
381
382 default := True
383
384 **validator**:
385 Sets schema validator for the data file.
386 Curren release relies on *jsonschema*, which
387 supports at the time of writing draft-03 and
388 draft-04.
389
390 The values are: ::
391
392 validator := (
393 MS_DRAFT3 | 'draft3'
394 | MS_DRAFT4 | 'draft4'
395 | MS_ON | 'on'
396 | MS_OFF | 'off'
397 | MODE_SCHEMA_DEFAULT | 'default'
398 )
399
400 default:= MS_OFF
401
402 **verbose**:
403 Extends the amount of the display of
404 processing data.
405
406 Returns:
407 instance/None
408
409 Raises:
410
411 NameError:
412
413 JSONDataValueError
414
415 jsonschema.ValidationError:
416
417 jsonschema.SchemaError:
418
419 """
420 if __debug__:
421 self.debug = False
422 else:
423 self.debug = kargs.get('debug', 0)
424
425 self.verbose = kargs.get('verbose', False)
426
427
428
429
430 self.op_cp_pol = kargs.get('copydata', self.op_cp_pol)
431 try:
432 self.op_cp_pol = copy2c[self.op_cp_pol]
433 except JSONDataKeyError:
434 raise JSONDataValueError('copydata', str(self.op_cp_pol))
435
436 self.indent_str = kargs.get('indent_str', 0)
437 self.sort_keys = kargs.get('sortstr', True)
438
439 self.jsonsyntax = kargs.get('jsonsyntax', JSYN_NATIVE)
440 if self.jsonsyntax not in (JSYN_NATIVE, 'json', JSYN_PYTHON, 'python'):
441 raise JSONDataError("Unknown syntax variant: jsonsyntax=" + str(self.jsonsyntax))
442
443 self.saveContext = kargs.get('saveContext', 0)
444 self.schema = kargs.get('schema', None)
445
446
447 validator = kargs.get('validator', self.validator)
448 try:
449 self.validator = validator2ms[validator]
450 except JSONDataKeyError:
451 raise JSONDataValueError('validator', str(validator))
452
453
454 if validator != MS_OFF:
455 if not self.schema:
456 raise JSONDataError("value", "schema", self.schema)
457
458
459 self.mode_json = kargs.get('mode', self.mode_json)
460 try:
461 self.mode_json = mode2mj[self.mode_json]
462 except JSONDataKeyError:
463 raise JSONDataParameterError("Unknown mode:" + str(self.mode_json))
464
465
466 _d = kargs.get('depth')
467 if _d:
468 if _d in ('default', -1):
469 self.op_depth = 0
470 elif _d.isnumeric():
471 self.op_depth = int(_d)
472 else:
473 raise JSONDataValueError("Not supported", str(_d))
474
475 if self.verbose:
476 print("VERB:JSON= " + str(myjson.__name__) + " / " + str(
477 myjson.__version__))
478
479
480 if kargs.get('data') and self.data is None:
481 raise JSONDataParameterError("value", "data", str(self.data))
482
483
484 if not self.schema and self.validator != MS_OFF:
485 raise JSONDataParameterError("value", "schema", str(self.schema))
486
487
488 if self.validator != MS_OFF:
489 self.validate(self.data, self.schema, self.validator)
490
492 return len(self.data)
493
495 """Deletes an item of *self.data*.
496
497 Args:
498 k:
499 Key or index of *self.data*.
500 If *None* the *self.data* is assigned *None*.
501
502 Returns:
503 None
504
505 Raises:
506 JSONDataKeyError
507
508 IndexError
509
510 """
511 if isinstance(self.data, (dict, list)):
512 del self.data[k]
513 raise JSONDataKeyError("requires object(dict) of array(list)")
514
516 """Returns a dictionary for objects as well as arrays.
517 Arrays are returned as a *dict* with interger indexes as keys,
518 while objects - *dict* - are returned by the native *items()*
519 call.
520
521 If required else use the attributes directly.
522
523 """
524 if type(self.data) is dict:
525 return self.data.items()
526 elif type(self.data) is list:
527 ret = {}
528 for i in range(len(self.data)):
529 ret[i] = self.data[i]
530 return ret.items()
531
533 """Returns a list of keys for objects, or a list of indexes
534 for arrays, thus enabling common access.
535
536 Args:
537 None
538
539 Returns:
540 For objects - dict - a list of keys,
541 for arrays - list - a list of integer indexes.
542
543 Raises:
544 pass-through
545 """
546 if type(self.data) is dict:
547 return self.data.keys()
548 elif type(self.data) is list:
549 ret = {}
550 for i in range(len(self.data)):
551 ret[i] = self.data[i]
552 return ret.items()
553
554
556 """Assigns a value to an item of *self.data*.
557 """
558 if isinstance(self.data, (list, dict,)):
559 self.data[k] = v
560 else:
561 raise JSONDataKeyError("requires object(dict) of array(list)")
562
564 """Context for processing of specific parameter setups.
565
566 Args:
567
568 Context Parameters:
569 See setargs. ::
570
571 copy, rtype, saveContext
572
573
574 Returns:
575
576 Raises:
577
578 """
579 if self.saveContext:
580 self.state_pre.append([
581 self.mode_json,
582 self.mode_schema,
583 self.mode_pointer,
584 self.mode_patch,
585 self.indent,
586 self.sort_keys,
587 self.validator,
588 self.op_depth,
589 self.op_ignore,
590 self.op_use,
591 ])
592 return self.deepcopy()
593
594 - def __exit__(self, exc_type, exc_value, traceback):
595 """Resets the context to the parameter setup before the last call of 'enter'.
596
597 """
598 if self.saveContext:
599 (self.mode_json, self.mode_schema, self.mode_pointer,
600 self.mode_patch, self.indent, self.sort_keys, self.validator,
601 self.op_depth, self.op_ignore, self.op_use,
602 ) = self.state_pre.pop()
603
605 """The boolean value of the contained data status *JSONData*.
606
607 Args:
608 None
609
610 Returns:
611 True: has any data
612 False: no data contained, this is also the case for empty *list* and *dict*
613
614
615 """
616 return self.data not in (None, {}, [])
617
619 """The boolean value of the contained data status *JSONData*.
620
621 Args:
622 None
623
624 Returns:
625 True: has any data
626 False: no data contained, this is also the case for empty *list* and *dict*
627
628
629 """
630 return self.data not in (None, {}, [])
631
633 """Evaluates the pointed value from the document.
634
635 The operation::
636
637 z = S(x)
638
639 Returns the top node referenced by the JSON-Pointer 'x' in accordance to RFC6901::
640
641 z = ( S => x )
642
643 Args:
644 *args:
645
646 args[0]:
647 An optional valid JSONPointer.
648
649 default:='' => top, see [RFC6901]_
650
651 **kargs:
652
653 **copydata**:
654 Use of input parameters for processing. ::
655
656 copydata := (
657 C_DEEP | 'deep'
658 | C_REF | 'ref'
659 | C_SHALLOW | 'shallow')
660
661 default := C_REF
662
663 Returns:
664 The pointed value, or None.
665
666 Raises:
667 JSONPointerError
668
669 """
670 if not args:
671 x = ''
672 else:
673 x = args[0]
674 c = kargs.get('copydata')
675 if c:
676 if c in ('deep', C_DEEP):
677 if isinstance(x, JSONPointer):
678 return copy.deepcopy(x.get_node_value(self.data))
679 return copy.deepcopy(
680 JSONPointer(x).get_node_value(self.data))
681 elif c in ('shallow', C_SHALLOW):
682 if isinstance(x, JSONPointer):
683 return copy.copy(x.get_node_value(self.data))
684 return copy.copy(JSONPointer(x).get_node_value(self.data))
685 elif c in ('ref', C_REF):
686 pass
687 else:
688 raise JSONDataError("Unknown copy type:" + str(c))
689
690 if isinstance(x, JSONPointer):
691 return x.get_node_value(self.data)
692 return JSONPointer(x).get_node_value(self.data)
693
695 """Compares this JSONData.data with x.
696
697 The operations: ::
698
699 S == x
700
701 Returns the result of comparison: ::
702
703 z = ( S == x )
704
705 Args:
706
707 x: A valid JSONData.
708
709 Context Parameters:
710
711 See setargs.
712
713 Returns:
714
715 True or False
716
717 Raises:
718
719 JSONDataError
720 """
721 if not self.data and not x:
722 return True
723 elif not self.data or not self.data:
724 return False
725
726 if type(x) in (dict, list, ):
727 return self.data == x
728 elif isinstance(x, JSONData):
729 return self.data == x.data
730 return self.data == x
731
733 """Dump data.
734 """
735 return repr(self.data)
736
738 """Dumps data by pretty print.
739
740 The data representation is controlled by the
741 variable 'self.jsonsyn' ::
742
743 JSONData.jsonscope := (JSYN_NATIVE | JSYN_PYTHON)
744
745 JSYN_NATIVE: "Literally in accordance to standards."
746 JSYN_PYTHON: "Python in-memory syntax representation."
747
748 """
749 if self.jsonsyn == JSYN_NATIVE:
750 return myjson.dumps(
751 self.data, indent=self.indent, sort_keys=self.sort_keys)
752 elif self.jsonsyn == JSYN_PYTHON:
753 s = myjson.dumps(
754 self.data, indent=self.indent, sort_keys=self.sort_keys)
755 s = re.sub("true", "True", s)
756 s = re.sub("false", "False", s)
757 s = re.sub("null", "None", s)
758 return s
759 else:
760 raise JSONDataError("JSON syntax variant not supported." + str(self.jsonsyn))
761
763 """Gets an entry of *self.data*.
764
765 Args:
766 sel:
767 Selector, either a key, or an index.
768
769 Returns:
770 The entry at the location,
771 or raises en exception.
772
773 Raises:
774 JSONDataKeyError(KeyError)
775
776 JSONDataIndexError(IndexError)
777
778 """
779 try:
780 return self.data[sel]
781 except KeyError:
782 raise JSONDataKeyError("object requires member name as key, got: " + str(sel))
783 except IndexError:
784 raise JSONDataIndexError("array requires index, got: " + str(sel))
785
787 """Provides an iterator for contained native data.
788 """
789 return iter(self.data)
790
792 """Compares this JSONData with x.
793
794 Args:
795 x:
796 Valid JSONData.
797
798 Returns:
799 True or False
800
801 Raises:
802 JSONDataError
803 """
804 return not self.__eq__(x)
805
806 - def branch_add(self, sourcenode, targetnode='', key=None, **kargs):
807 """Add a complete branch into a target structure of type object.
808 Present branches are replaced, non-existent branches are
809 added.
810
811 The parent of the insertion point has to exist by default,
812 see [RFC6902]_. If the target is an array, the source is either
813 appended, or inserted [RFC6902]_.
814
815 Args:
816 **sourcenode**:
817 Source branch to be inserted into the target tree.
818 Either from within self-data, or an external source: ::
819
820 sourcenode := <commonnode> # see class header
821
822 **targetnode**:
823 Target node within self-data, where the branch is to be inserted. ::
824
825 targetnode := <innode> # see class header
826
827 default := '' # the top of the 'whole document'
828
829 **key**:
830 Hook for the insertion within the target node. If not
831 provided the contents of the target node itself are
832 replaced by the source node.
833
834 default := None
835
836 kargs:
837 **copydata**:
838 The type of creation of the added branch. ::
839
840 copydata := (
841 C_REF # copy the reference only
842 | C_DEEP # call copy.deepcopy()
843 | C_SHALLOW # call copy.copy()
844 )
845
846 default := C_DEEP
847
848 Returns:
849 When successful returns *True*, else returns
850 either *False*, or raises an exception.
851
852 Raises:
853 JSONDataNodeError:
854 The target node is not contained in current object.
855
856 JSONDataNodeTypeError:
857 The types mismatch.
858
859 JSONDataKeyError:
860 Key mismatch.
861
862 """
863 ret = False
864 _copy = kargs.get('copydata', C_DEEP)
865
866 def _cp(v):
867 if _copy == C_DEEP:
868 return copy.deepcopy(v)
869 elif _copy == C_SHALLOW:
870 return copy.copy(v)
871 if _copy == C_REF:
872 return v
873 else:
874 return copy.deepcopy(v)
875
876 if isinstance(sourcenode, JSONPointer):
877 sourcenode = sourcenode(self.data, False)
878 elif type(sourcenode) in ISSTR:
879 try:
880 sourcenode = JSONPointer(sourcenode)
881 except JSONPointerTypeError:
882
883 if sourcenode[0] == '/':
884
885 raise
886
887 elif isinstance(sourcenode, JSONData):
888 sourcenode = sourcenode.data
889
890
891 if targetnode is "":
892 targetnode = self.data
893 elif type(targetnode) in ISSTR:
894 targetnode = JSONPointer(targetnode)
895 elif isinstance(targetnode, JSONData):
896 targetnode = targetnode.data
897
898 if isinstance(targetnode, JSONPointer):
899 try:
900 if not key:
901 targetnode, key = targetnode.get_node_and_key(self.data)
902 else:
903 targetnode = targetnode(self.data, False)
904 except JSONDataKeyError:
905 raise
906 except Exception as e:
907
908 if key:
909 if type(key) == int:
910 self.branch_create('', targetnode, [])
911 else:
912 self.branch_create('', targetnode, {})
913 if not key:
914 targetnode, key = targetnode.get_node_and_child(self.data)
915 else:
916 targetnode = targetnode(self.data)
917
918 if type(targetnode) == dict:
919 if key:
920 targetnode[key] = _cp(sourcenode)
921 else:
922 if type(sourcenode) != dict:
923 raise JSONDataNodeTypeError(
924 "type", "dict-target requires a key:targetnode/sourcenode",
925 str(type(targetnode)) + "/" + str(type(sourcenode)))
926 for k, v in sourcenode.items():
927 targetnode[k] = _cp(v)
928
929 return True
930
931 elif type(targetnode) == list:
932 if key is None:
933
934
935 if type(sourcenode) is list:
936 targetnode.extend(_cp(sourcenode))
937
938
939 elif type(sourcenode) is dict:
940 targetnode.append(sourcenode)
941
942 elif key == '-' or key == len(targetnode):
943 targetnode.append(_cp(sourcenode))
944 ret = True
945
946 elif 0 <= key < len(targetnode):
947 targetnode.insert(key, _cp(sourcenode))
948
949 else:
950 raise JSONDataKeyError(
951 "mismatch:node:type", 'key', key, 'key-type',
952 type(key), 'node-type', type(targetnode))
953
954 return True
955
956 else:
957 raise JSONDataNodeTypeError(
958 "type", "requires array or object for targetnode: targetnode/sourcenode",
959 str(type(targetnode)) + "/" + str(type(sourcenode)))
960
961 return ret
962
963 - def branch_copy(self, sourcenode, targetnode='/', key=None, force=True):
964 """Copies the source branch to the target node.
965 The *branch_copy* is internally mapped to the call *branch_add*,
966 thus shares basically the same parameters and behavior.
967
968 Args:
969
970 **sourcenode**:
971 Source branch to be copied into target tree.
972 Either from within self-data, or an external source: ::
973
974 sourcenode := <commonnode> # see class header
975
976 **targetnode**:
977 Target node for the branch. Either from within self-data,
978 or an external source: ::
979
980 targetnode := <innode> # see class header
981
982 default := '/'
983
984 **key**:
985 Optional key for the insertion point within target
986 node, if not provided the target node itself.
987
988 **force**:
989 If true present are replaced, else only non-present
990 targets are copied.
991
992 default := True
993
994 Returns:
995 When successful returns *True*, else returns either *False*,
996 or raises an exception.
997
998 Raises:
999 JSONDataError
1000
1001 pass-through
1002 """
1003 if force:
1004 return self.branch_add(sourcenode, targetnode, key)
1005
1006 elif type(targetnode) == list:
1007 if key == '-':
1008 pass
1009 elif not key is None:
1010 if 0 <= key < len(targetnode):
1011 if targetnode[key]:
1012 raise JSONDataError("Node exists.")
1013 if len(targetnode) > len(sourcenode):
1014 raise JSONDataError("Node exists.")
1015 else:
1016 if type(sourcenode) is list:
1017 if targetnode:
1018 raise JSONDataError("Node exists.")
1019
1020 elif type(targetnode) == dict:
1021 if key:
1022 if targetnode.get(key, None):
1023 raise JSONDataError("Node exists.")
1024 else:
1025 if type(sourcenode) is dict:
1026 if targetnode:
1027 raise JSONDataError("Node exists.")
1028
1029 return self.branch_add(sourcenode, targetnode, key)
1030
1031 - def branch_create(self, branchpath, targetnode=None, padding_value=None):
1032 """Creates an abitrary relative branch from a path description
1033 located at *targetnode*. Intermediate nodes are created automatically
1034 when missing.
1035
1036 The requested branch is created as the relative child branch
1037 of the provided *targetnode*. The *targetnode* must exist,
1038 while the child node - *branchpath[0]* - must not.
1039
1040 Args:
1041
1042 **branchpath**:
1043 New branch to be created in the target node.
1044 A Pointer address path relative to the *targetnode*. ::
1045
1046 branchpath := <commonnode> # see class header
1047
1048 **targetnode**:
1049 Base node for the created branch, must exist
1050 and located within the data tree of current object. ::
1051
1052 targetnode := <innode> # see class header
1053
1054 default := "/"
1055
1056 **padding_value**:
1057 Optional default value, either an atomic type or a sub-branch
1058 itself. This value is only used for a new leaf, in case of
1059 an existent node the value is ignored.
1060
1061 default := "null" / *None*
1062
1063 Returns:
1064 When successful returns the created node, else returns
1065 either *None*, or raises an exception.
1066
1067 Raises:
1068 JSONDataError
1069
1070 JSONDataKeyError
1071
1072 JSONDataNodeTypeError
1073
1074 JSONDataParameterError
1075
1076 JSONDataPathError
1077 """
1078 ret = None
1079
1080 def get_newnode_of_type(bkeytype):
1081 """Fetch the required type for the new container."""
1082 if not bkeytype:
1083 return None
1084
1085 if len(bkeytype) < 2:
1086 if bkeytype[0] == '-':
1087 return []
1088 elif type(bkeytype[0]) is int:
1089 return []
1090 elif type(bkeytype[0]) in (
1091 str,
1092 unicode, ):
1093 return {}
1094 else:
1095 if bkeytype[1] == '-':
1096 return []
1097 elif type(bkeytype[1]) is int:
1098 return []
1099 elif type(bkeytype[1]) in (
1100 str,
1101 unicode, ):
1102 return {}
1103
1104 raise JSONDataKeyError("type", 'keytype', str(bkeytype))
1105
1106
1107
1108 if type(branchpath) in ISSTR:
1109 branchpath = JSONPointer(branchpath)
1110
1111 if not isinstance(branchpath, list):
1112 raise JSONDataPathError("type", "branchpath", branchpath)
1113
1114
1115
1116 if targetnode is None:
1117 tnode = self.data
1118
1119 elif isinstance(targetnode, JSONPointer):
1120 try:
1121 tnode = targetnode(self.data)
1122
1123 except TypeError:
1124 raise JSONDataNodeTypeError(
1125 "Requires container, got type='%s' in target node:'%s'" %
1126 (str(type(tnode)), str(targetnode.get_raw()) ))
1127
1128 except (KeyError, JSONPointerError):
1129 raise JSONDataKeyError(
1130 "Requires present node, missing target node:'%s'" %
1131 (str(targetnode.get_raw()) ))
1132
1133 elif targetnode == '':
1134 tnode = self.data
1135
1136 else:
1137 tnode = targetnode
1138
1139 if type(tnode) == dict:
1140
1141
1142
1143 if type(branchpath[0]) not in ISSTR:
1144 raise JSONDataPathError(
1145 "type", "branchpath",
1146 str(type(tnode)) + "/" +str(branchpath))
1147
1148 if len(branchpath) > 1:
1149
1150 if not tnode.get(unicode(branchpath[0]), False):
1151 tnode[unicode(branchpath[0])] = get_newnode_of_type(branchpath)
1152
1153 ret = self.branch_create(
1154 branchpath[1:], tnode[branchpath[0]], padding_value)
1155
1156 else:
1157
1158
1159 if not tnode.get(branchpath[0], False):
1160 ret = tnode[unicode(branchpath[0])] = self.get_canonical_value(padding_value)
1161 else:
1162 ret = tnode[unicode(branchpath[0])]
1163
1164 elif type(tnode) == list:
1165
1166
1167
1168 if type(branchpath[0]) in (int, ) and branchpath[0] <= len(tnode):
1169 pass
1170 elif unicode(branchpath[0]) == u'-':
1171 pass
1172 else:
1173 raise JSONDataNodeTypeError(
1174 "index-value", "targetnode/branch:" + str(type(tnode))
1175 + " list-index requires int(i <= len()) or '-', got "
1176 + str(type(branchpath[0])) + " '" + str(branchpath) + "'"
1177 )
1178
1179 if len(branchpath) == 1:
1180 if branchpath[0] == '-':
1181 branchpath[0] = len(tnode)
1182 tnode.append(self.get_canonical_value(padding_value))
1183 else:
1184 tnode[branchpath[0]] = self.get_canonical_value(padding_value)
1185 ret = tnode
1186 else:
1187 if branchpath[0] == '-':
1188 tnode.append(get_newnode_of_type(branchpath))
1189 ret = self.branch_create(
1190 branchpath[1:], tnode[-1], padding_value)
1191 elif tnode != None and branchpath[0] == len(tnode):
1192 tnode.append(get_newnode_of_type(branchpath))
1193 ret = self.branch_create(
1194 branchpath[1:], tnode[-1], padding_value)
1195
1196 else:
1197 raise JSONDataNodeTypeError(
1198 "type", "existing targetnode", str(targetnode) + " = "+ str(type(tnode)) + " requires: list or dict")
1199
1200 return ret
1201
1202 - def branch_move(self,
1203 sourcenode,
1204 targetnode=None,
1205 key=None,
1206 force=False):
1207
1208 """Moves a branch to the target node.
1209
1210 Args:
1211
1212 **sourcenode**:
1213 Source branch to be moved into the target node.
1214 Must be member of the self-data structure, else use
1215 either *branch_add* or *branch_copy*.
1216 Either from within self-data, or an external source: ::
1217
1218 sourcenode := <innode> # see class header
1219
1220 **targetnode**:
1221 Target node for the branch. ::
1222
1223 targetnode := <innode> # see class header
1224
1225 default := '/'
1226
1227 **key**:
1228 Optional key for the insertion point within target
1229 node, if not provided the target node itself.
1230
1231 **force**:
1232 If true present are replaced, else only non-present
1233 are copied.
1234
1235 default := True
1236
1237 Returns:
1238 When successful returns *True*, else returns either
1239 *None*, or raises an exception.
1240
1241 Raises:
1242 JSONDataError
1243
1244 JSONDataKeyError
1245 """
1246 ret = self.branch_copy(sourcenode, targetnode, key, force)
1247 if ret:
1248 ret1 = self.branch_remove(sourcenode)
1249 return ret & ret1
1250 return ret
1251
1253 """Removes a branch from a contained data in self.
1254
1255 Args:
1256
1257 **targetnode**:
1258 Container with item to be removed. ::
1259
1260 targetnode := <innode> # see class header
1261
1262 **key**:
1263 Key of insertion point within target node, if not
1264 provided the target node itself.
1265
1266 **rfc6902**:
1267 If *True* the removed element has to be present,
1268 else non-present is simply ignored.
1269
1270 Returns:
1271 When successful returns *True*, else returns
1272 either *False*, or raises an exception.
1273
1274 Raises:
1275 JSONDataKeyError
1276
1277 JSONDataNodeTypeError
1278 """
1279 if type(targetnode) in ISSTR:
1280 _targetnode = JSONPointer(targetnode)
1281
1282 elif type(targetnode) is list:
1283 _targetnode = JSONPointer(targetnode)
1284
1285 elif isinstance(targetnode, JSONPointer):
1286 _targetnode = targetnode
1287
1288 else:
1289 raise JSONDataNodeTypeError("Requires path within self:got:" + str(type(targetnode)))
1290
1291 try:
1292 if not key:
1293 _targetnode = targetnode(self.data, True)
1294 key = targetnode[-1]
1295 else:
1296 _targetnode = targetnode(self.data, False)
1297
1298 except (KeyError, TypeError, IndexError):
1299 raise JSONDataKeyError("Key not found:" + str(key))
1300
1301 except JSONPointerError:
1302 if rfc6902:
1303 raise JSONDataKeyError("Requires present node" + str(targetnode) + str(key))
1304 return True
1305
1306 try:
1307 _targetnode.pop(key)
1308 return True
1309 except (IndexError, TypeError, KeyError):
1310 if rfc6902:
1311 raise JSONDataKeyError("Missing node within self: " + str(targetnode))
1312 return True
1313
1314 - def branch_replace(self, sourcenode, targetnode, key=None, rfc6902=True):
1315 """Replaces the value of the target node by the copy
1316 of the source branch.
1317
1318 Requires in order to RFC6902, all items to be replaced
1319 has to be present. Thus fails by default if at least one
1320 is missing.
1321
1322 Internally the 'branch_add()' call is used with a deep copy.
1323 When a swallow copy is required the 'branch_move()' has to be used.
1324
1325 Args:
1326 **sourcenode**:
1327 Source branch to be inserted into target tree.
1328 Either from within self-data, or an external source: ::
1329
1330 sourcenode := <commonnode> # see class header
1331
1332 **targetnode**:
1333 Target where the branch is inserted. ::
1334
1335 targetnode := <innode> # see class header
1336
1337 **key**:
1338 Key of insertion point within target node, if not
1339 provided the target node itself.
1340
1341 **rfc6902**:
1342 If *True* the removed element has to be present,
1343 else non-present is simply ignored.
1344
1345 Returns:
1346 When successful returns *True*, else returns
1347 either *False*, or raises an exception.
1348
1349 Raises:
1350 JSONDataError
1351 """
1352 if isinstance(targetnode, JSONPointer):
1353 try:
1354 if not key:
1355 tnode, key = targetnode.get_node_and_key(self.data)
1356 else:
1357 tnode = targetnode(self.data, False)
1358
1359 except TypeError:
1360 raise JSONDataNodeTypeError(
1361 "Requires container, got type='%s' in target node:'%s'" %
1362 (str(type(tnode)), str(targetnode.get_raw()) ))
1363
1364 except (KeyError, JSONPointerError):
1365 if rfc6902:
1366 raise JSONDataKeyError(
1367 "Requires present node, missing key='%s' in target node:'%s'" %
1368 (str(key), str(targetnode.get_raw()) ))
1369
1370 elif type(targetnode) in (list,):
1371 try:
1372 if not key:
1373 tnode, key = JSONPointer(targetnode).get_node_and_key(self.data)
1374 else:
1375 tnode = JSONPointer(targetnode)(self.data, False)
1376
1377 except (KeyError, JSONPointerError):
1378 if rfc6902:
1379 if len(str(targetnode)) > 30:
1380 targetnode = str(targetnode)[:30] + '...'
1381 raise JSONDataKeyError(
1382 "Requires present node, missing key='%s' in target node:'%s'" %
1383 (str(key), str(targetnode) ))
1384 else:
1385 if len(str(targetnode)) > 30:
1386 targetnode = str(targetnode)[:30] + '...'
1387 raise JSONDataNodeTypeError(
1388 "Requires parent container-node, got target node:'%s: %s'" %
1389 (str(type(targetnode)), str(targetnode)))
1390
1391 return self.branch_add(sourcenode, tnode, key)
1392
1394 """Superposes a branch recursively on to the current data tree
1395 *self.data* with defined constraints. Provides partial mapping
1396 in dependence of parameters and data entries of source and/or
1397 target.
1398
1399 The processing is controlled by logic operations on node
1400 structures, which maps a logical tree of JSON nodes from the
1401 source node onto the subtree of JSON nodes defined by the target
1402 target node. The provided logic operators are related to structure,
1403 though the types of nodes, not the contents.
1404
1405 For the provided logic operators refer to parameter *map*.
1406
1407 Args:
1408
1409 **sourcenode**:
1410 Value struct to be inserted.
1411
1412 default := None
1413
1414 **targetnode**:
1415 Data node within current document to be superposed.
1416
1417 default := None # whole document *self.data*
1418
1419 **key**:
1420 Hook selector within the data tree spanned by the targetnode .
1421
1422 default := None # top of the targetnode
1423
1424 kargs:
1425 **copy**:
1426 Create a copy of the sourcenode. ::
1427
1428 copy := (
1429 C_DEEP # insert from copy.deepcopy()
1430 | C_SHALLOW # insert from copy.copy()
1431 | C_REF # insert from the provided parameter
1432 )
1433
1434 default := C_REF # no copy, work on input
1435
1436 **depth**:
1437 Sets the default behavior for the operators
1438 on the data branches. Controls, whether the hook
1439 only or the complete branch is processed in
1440 depth node-by-node. ::
1441
1442 0: the hook of the branch
1443 #n: the level of the branch as integer, the
1444 remaining sub-branch is treated by it's hook
1445 -1: the complete branch
1446
1447 default:= 0
1448
1449 **ignore**:
1450 Ignores attributes including subtrees contained
1451 in the list. ::
1452
1453 ignore := [<list-of-rfc6901-path-items>]
1454
1455 **map**:
1456 Sets the default behavior for the mapping of
1457 branches by operators. This is also influenced
1458 by the parameter 'op_depth'. ::
1459
1460 B_AND: replace corresponding leafs only when any target node is present
1461 B_OR: replace corresponding items of source leafs
1462 B_XOR: insert only when no target node is present
1463
1464 default:= B_OR
1465
1466 **use**:
1467 Considers the listed attributes only as white list. ::
1468
1469 use := [<list-of-rfc6901-path-items>]
1470
1471 Returns:
1472 *True* for success, else *False* or raises exception.
1473
1474 Raises:
1475 JSONDataError
1476
1477 JSONDataIndexError
1478
1479 pass-through
1480
1481 """
1482
1483 _cp = C_REF
1484 _ign = self.op_ignore
1485 _use = self.op_use
1486 _dep = self.op_depth
1487
1488 _mp = B_OR
1489
1490 def _map(d0, s0, v0, n=0):
1491 """The core algorithm for the mapping of branches of Python
1492 structures by constraints. Includes resulting logic operations
1493 from constraints.
1494
1495 The code tends to be monolithic, but saves memory and performance.
1496
1497 Args:
1498 d0:
1499 Data root.
1500
1501 s0:
1502 Key or index for hook.
1503
1504 v0:
1505 Value/branch to be hooked.
1506
1507 Environment:
1508 Uses inherited variables from parent
1509 name spaces.
1510
1511 Returns:
1512
1513 Raises:
1514
1515 """
1516 if self.op_ignore and s0 in self.op_ignore:
1517 return
1518 if self.op_use and s0 not in self.op_use:
1519 return
1520 if self.op_depth > 0 and n >= self.op_depth:
1521 return
1522 n += 1
1523
1524 if s0 != None:
1525 try:
1526 dx = d0
1527 d0 = d0[s0]
1528 except TypeError:
1529 if s0 == '-':
1530 d0.append(v0)
1531 return True
1532 raise
1533 except KeyError:
1534 d0[s0] = v0
1535 return True
1536 except IndexError:
1537 if len(d0) == s0:
1538 d0.append(v0)
1539 return True
1540 raise JSONDataIndexError("out of range(>%s): %s" % (str(len(d0)), str(s0)))
1541
1542 else:
1543 dx = d0
1544
1545 if _mp in (B_OR,):
1546 if type(d0) is dict:
1547 if type(v0) is dict:
1548 for k, v in v0.items():
1549 _map(d0, k, v, n + 1)
1550 elif s0 != None:
1551 dx[s0] = v0
1552 else:
1553 dx = v0
1554
1555 elif type(d0) is list:
1556 if type(v0) is list:
1557 for k in range(len(v0)):
1558 _map(d0, k, v0[k], n + 1)
1559 else:
1560 if type(s0) is int:
1561 if s0 < len(dx):
1562 dx[s0] = v0
1563 elif s0 == len(dx):
1564 dx.append(v0)
1565 else:
1566 raise JSONDataIndexError("out of range(>%s): %s" % (str(len(dx)), str(s0)))
1567 elif type(s0) in ISSTR:
1568 dx[s0] = v0
1569 return True
1570
1571 else:
1572 dx[s0] = v0
1573
1574 elif _mp in (B_XOR, ):
1575 if type(d0) is dict:
1576 if not d0.get(s0):
1577 if type(v0) is dict:
1578 for k, v in v0.items():
1579 _map(d0, k, v, n + 1)
1580 elif s0 == '':
1581 pass
1582 else:
1583 d0[s0] = v0
1584
1585 elif type(d0) is list:
1586 if type(v0) is list:
1587 for k in range(len(v0)):
1588 _map(d0, k, v0[k], n + 1)
1589
1590 else:
1591 if type(dx) is dict:
1592 if dx.get(s0):
1593 dx[s0] = v0
1594 elif type(dx) is list:
1595 if len(dx) <= s0:
1596 dx.append(v0)
1597 else:
1598
1599 raise JSONDataError("internal error")
1600 return True
1601
1602 else:
1603 if type(dx) is dict:
1604 if not dx.get(s0):
1605 dx[s0] = v0
1606 elif type(dx) is list:
1607 if len(dx) == s0:
1608 dx.append(v0)
1609 else:
1610
1611 raise JSONDataError("internal error")
1612
1613 elif _mp in (B_AND, ):
1614 if type(d0) is dict:
1615 if not d0.get(s0):
1616 for k, v in v0.items():
1617 if d0.get(k):
1618 _map(d0, k, v, n + 1)
1619
1620 elif type(d0) is list:
1621
1622 if type(v0) is list:
1623 for k in range(len(v0)):
1624 if k < len(d0):
1625 _map(d0, k, v0[k], n + 1)
1626 else:
1627 if type(dx) is dict:
1628 if dx.get(s0):
1629 dx[s0] = v0
1630 elif type(dx) is list:
1631 if len(dx) <= s0:
1632 dx.append(v0)
1633 else:
1634
1635 raise JSONDataError("internal error")
1636 return True
1637
1638 else:
1639 dx[s0] = v0
1640
1641 else:
1642 return False
1643 return True
1644
1645
1646 for k, v in kargs.items():
1647 if k == 'copy':
1648 if v in (
1649 'deep',
1650 C_DEEP, ):
1651 _cp = C_DEEP
1652
1653 elif v in (
1654 'shallow',
1655 C_SHALLOW, ):
1656 _cp = C_SHALLOW
1657
1658 elif v in (
1659 'ref',
1660 C_REF, ):
1661 _cp = C_REF
1662
1663 else:
1664 raise JSONDataError("Unknown copy-type:" + str(v))
1665
1666 elif k == 'ignore':
1667 _ign = v
1668
1669 elif k == 'use':
1670 _use = v
1671
1672 elif k == 'depth':
1673 _dep = v
1674
1675 elif k == 'map':
1676
1677 if v in (
1678 B_AND,
1679 B_OR,
1680 B_XOR,
1681 ):
1682 _mp = v
1683
1684 else:
1685 raise JSONDataError("Unknown map:" + str(v))
1686
1687 if targetnode is None:
1688
1689 targetnode = self.data
1690
1691 elif type(targetnode) is list and (
1692 (key and type(key) not in (int, float,))
1693 and key != '-'
1694 ):
1695
1696 raise JSONDataError("list index mismatch" + str(key))
1697
1698 elif type(targetnode) is dict:
1699
1700 pass
1701
1702 elif isinstance(targetnode, JSONPointer):
1703
1704 targetnode = targetnode(self.data)
1705
1706 elif type(targetnode) in ISSTR:
1707
1708 targetnode = JSONPointer(targetnode)(self.data)
1709
1710
1711
1712
1713 if key == None and (
1714 type(sourcenode) not in (dict, list)
1715 or type(sourcenode) != type(targetnode)
1716 ):
1717 if not self.mode_json & MJ_RFC7159 and type(sourcenode) not in (dict, list):
1718 raise JSONDataError("basic document types require mode RFC7159: " + str(sourcenode))
1719
1720 if _mp in (
1721 B_OR,
1722 ):
1723 self.data = sourcenode
1724 elif _mp in (
1725 B_AND,
1726 ) and (
1727 (self.data and sourcenode)
1728 or (self.data == None and sourcenode == None)
1729 or (self.data != None and sourcenode != None)
1730 ):
1731 self.data = sourcenode
1732 elif _mp in (
1733 B_XOR,
1734 ) and not (
1735 (self.data and sourcenode)
1736 or (self.data == None and sourcenode == None)
1737 or (self.data != None and sourcenode != None)
1738 ):
1739 self.data = sourcenode
1740
1741 return True
1742
1743 if _cp is C_DEEP:
1744 _dat = copy.deepcopy(targetnode)
1745
1746 elif _cp is C_REF:
1747 _dat = targetnode
1748
1749 elif _cp is C_SHALLOW:
1750 _dat = copy.copy(targetnode)
1751
1752 if _mp in (
1753 B_OR,
1754 B_XOR,
1755 B_AND, ):
1756 _map(_dat, key, sourcenode, n=0)
1757
1758 return True
1759
1761 """Tests match in accordance to RFC6902.
1762
1763 Args:
1764 **targetnode**:
1765 Node to be compared with the value. Due to
1766 ambiguity the automated conversion is not
1767 reliable, thus it has to be valid. ::
1768
1769 targetnode := <innode> # see class header
1770
1771 **value**:
1772 Expected value for the given node.
1773
1774 Returns:
1775 When successful returns 'True', else returns 'False'.
1776
1777 Raises:
1778 JSONDataError
1779 """
1780 if not targetnode and not value:
1781 return True
1782 elif type(targetnode) in ISSTR:
1783 return JSONPointer(targetnode).get_node_value(self.data) == value
1784 elif isinstance(targetnode, JSONPointer):
1785 return targetnode.get_node_value(self.data) == value
1786
1788 """Returns the reference to data."""
1789 return self.data
1790
1792 """Returns the reference to schema."""
1793 return self.schema
1794
1796 """Clears the contained data.
1797 """
1798 if isinstance(self.data, dict):
1799 self.data.clear()
1800 elif isinstance(self.data, list):
1801 [x.pop() for x in reversed(self.data)]
1802 else:
1803 self.data = None
1804 return True
1805
1807 """Creates a shallow copy of self.
1808 """
1809 return JSONData(self.data, copydata=C_SHALLOW)
1810
1812 """Creates a deep copy of self, including referenced data.
1813 The schema is kept as a shared reference.
1814 """
1815 return JSONData(self.data, copydata=C_DEEP)
1816
1817 - def get(self, key, default=None):
1818 """Transparently passes the 'get()' call to 'self.data'."""
1819 return self.data.get(key, default)
1820
1822 """Fetches a copy of the canonical value represented by
1823 the node. The actual value could be either an atomic value,
1824 a node representing a branch, or a reference to
1825 an atomic value.
1826 Creates a deep copy, thus references are no longer
1827 valid.
1828
1829 Args:
1830 **value**:
1831 Value pointer to be evaluated to the actual
1832 value. Valid input types are:
1833
1834 int,str,unicode:
1835 Integer, kept as an atomic integer
1836 value.
1837
1838 dict,list:
1839 Assumed to be a valid node for 'json'
1840 package, used by reference.
1841
1842 JSONPointer:
1843 A JSON pointer in accordance to
1844 RFC6901.
1845
1846 Returns:
1847 When successful returns the value, else returns
1848 either 'False', or raises an exception.
1849
1850 Raises:
1851 JSONDataError
1852
1853 """
1854 if type(node) in (dict, list):
1855 return node
1856 elif type(node) in (
1857 int,
1858 float, ):
1859 return node
1860 elif type(node) in (
1861 str,
1862 unicode, ):
1863 return unicode(node)
1864 elif isinstance(node, JSONPointer):
1865 return node.get_node_value(self.data, C_DEEP)
1866 elif not node:
1867 return None
1868 else:
1869 raise JSONDataError("type", "value", str(node))
1870
1871 - def pop(self, key):
1872 """Transparently passes the 'pop()' call to 'self.data'."""
1873 return self.data.pop(key)
1874
1876 """Prints structured data.
1877
1878 Args:
1879
1880 **pretty**:
1881 Activates pretty printer, else flat. ::
1882
1883 format := (
1884 PJ_TREE # tree view JSON syntax
1885 | PJ_FLAT # flat print JSON syntax
1886 | PJ_PYTREE # tree view Python syntax
1887 | PJ_PYFLAT # flat print Python syntax
1888 | PJ_REPR # repr() - raw string, Python syntax
1889 | PJ_STR # str() - formatted string, Python syntax
1890 )
1891
1892 kargs:
1893 **source**:
1894 Prints data within 'source'.
1895
1896 default:=self.data
1897
1898 Returns:
1899 When successful returns 'True', else returns either
1900 'False', or raises an exception.
1901
1902 Raises:
1903 pass-through
1904 """
1905 source = kargs.get('source', source = self.data)
1906
1907 if pretty == PJ_TREE:
1908 print(myjson.dumps(source, indent=self.indent))
1909 elif pretty == PJ_FLAT:
1910 print(myjson.dumps(source))
1911 elif pretty == PJ_PYTREE:
1912 print(myjson.dumps(source, indent=self.indent))
1913 elif pretty == PJ_PYFLAT:
1914 print(str(source))
1915 elif pretty == PJ_REPR:
1916 print(repr(source))
1917 elif pretty == PJ_STR:
1918 print(str(source))
1919 else:
1920 print(myjson.dumps(source, indent=self.indent))
1921
1922
1924 """Prints structured schema.
1925
1926 Args:
1927
1928 **pretty**:
1929 Activates pretty printer for treeview,
1930 else flat.
1931
1932 kargs:
1933 **source**:
1934 Prints schema within 'source'.
1935
1936 default:=self.schema
1937
1938 Returns:
1939 When successful returns 'True', else returns
1940 either 'False', or raises an exception.
1941
1942 Raises:
1943 pass-through
1944
1945 """
1946 source = kargs.get('source', self.schema)
1947
1948 if pretty:
1949 print(myjson.dumps(source, indent=self.indent))
1950 else:
1951 print(myjson.dumps(source))
1952
1953 - def set_schema(self, schemafile=None, targetnode=None, **kargs):
1954 """Sets schema or inserts a new branch into the current
1955 assigned schema.
1956
1957 The main schema(targetnode==None) is the schema related
1958 to the current instance. Additional branches could be added
1959 by importing the specific schema definitions into the main
1960 schema. These could either kept volatile as a temporary
1961 runtime extension, or stored into a new schema file in order
1962 as extension of the original for later combined reuse.
1963
1964 Args:
1965 **schemafile**:
1966 JSON-Schema filename for validation of the
1967 subtree/branch. See also **kargs['schema'].
1968
1969 **targetnode**:
1970 Target container hook for the inclusion of
1971 the loaded branch.
1972
1973 kargs:
1974 **schema**:
1975 In-memory JSON-Schema as an alternative
1976 to schemafile. When provided the 'schemafile'
1977 is ignored.
1978
1979 default:=None
1980
1981 **validator**:
1982 Sets schema validator for the data file.
1983 The values are: ::
1984
1985 validator := (
1986 default = validate,
1987 | draft3 = Draft3Validator,
1988 | off = None.
1989 )
1990
1991 default:= validate
1992
1993 **persistent**:
1994 Stores the 'schema' persistently into 'schemafile'
1995 after completion of update including addition of
1996 branches. Requires valid 'schemafile'.
1997
1998 default:=False
1999
2000 Returns:
2001 When successful returns 'True', else returns either
2002 'False', or raises an exception.
2003
2004 Raises:
2005
2006 JSONDataError
2007
2008 JSONDataSourceFileError
2009
2010 JSONDataValueError
2011
2012 """
2013 if __debug__:
2014 if self.debug:
2015 print("DBG:set_schema:schemafile=" + str(schemafile))
2016
2017
2018
2019
2020 persistent = False
2021 schema = None
2022 for k, v in kargs.items():
2023 if k == 'validator':
2024 if v == 'default' or v == MS_DRAFT4:
2025 self.validator = MS_DRAFT4
2026 elif v == 'draft3' or v == MS_DRAFT3:
2027 self.validator = MS_DRAFT3
2028 elif v == 'off' or v == MS_OFF:
2029 self.validator = MS_OFF
2030 else:
2031 raise JSONDataValueError("unknown", k, str(v))
2032 elif k == 'schema':
2033 schema = v
2034 elif k == 'persistent':
2035 persistent = v
2036
2037 if schemafile != None:
2038 self.schemafile = schemafile
2039 elif self.schemafile != None:
2040 schemafile = self.schemafile
2041
2042 if not schemafile:
2043 if persistent:
2044 raise JSONDataTargetFileError("open", "JSONSchemaFilename",
2045 schemafile)
2046
2047
2048 if schema:
2049 pass
2050
2051 elif schemafile:
2052 schemafile = os.path.abspath(schemafile)
2053 self.schemafile = schemafile
2054 if not os.path.isfile(schemafile):
2055 raise JSONDataSourceFileError("open", "schemafile", str(schemafile))
2056 with open(schemafile) as schema_file:
2057 schema = myjson.load(schema_file)
2058 if schema == None:
2059 raise JSONDataSourceFileError("read", "schemafile", str(schemafile))
2060
2061 else:
2062 raise JSONDataSourceFileError("open", "schemafile", str(schemafile))
2063
2064
2065
2066
2067 if not targetnode:
2068 self.schema = schema
2069
2070 else:
2071
2072
2073 if type(targetnode) != type(schema):
2074 raise JSONDataError(
2075 "type", "target!=branch",
2076 str(type(targetnode)) + "!=" + str(type(schema)))
2077
2078 self.branch_add(targetnode, None, schema)
2079
2080 return schema != None
2081
2082 - def validate(self, data, schema, validator=None):
2083 """Validate data with schema by selected validator.
2084
2085 Args:
2086
2087 **data**:
2088 JSON-Data.
2089
2090 **schema**:
2091 JSON-Schema for validation.
2092
2093 **validator**:
2094 Validator to be applied, current supported:
2095
2096 schema:
2097
2098 In-memory JSON-Schema as an alternative
2099 to schemafile. When provided the 'schemafile'
2100 is ignored.
2101
2102 default:=None
2103
2104 **validator**: [default, draft3, draft4, off, on, ]
2105 Sets schema validator for the data file.
2106
2107 default|MS_ON:
2108 The current default.
2109
2110 draft3|MS_DRAFT3:
2111 The first supported JSONSchema IETF-Draft.
2112
2113 draft4|MS_DRAFT4:
2114 The current supported JSONSchema IETF-Draft.
2115
2116 off|MS_OFF:
2117 No validation.
2118
2119 default:= MS_DRAFT4
2120
2121 Returns:
2122 When successful returns 'True', else returns
2123 either 'False', or raises an exception.
2124
2125 Raises:
2126
2127 JSONDataValidationError
2128
2129 JSONDataSchemaError
2130
2131 JSONDataValueError
2132 """
2133 if not validator:
2134 validator = self.mode_schema
2135
2136 if validator == MS_DRAFT4:
2137 if self.verbose:
2138 print("VERB:Validate: draft4")
2139 try:
2140 jsonschema.validate(data, schema)
2141
2142 except JSONDataValidationError as e:
2143 print("ValidationError"
2144 + "\n" + str(e)
2145 + "\n#---"
2146 + "\n" + str(dir(e))
2147 + "\n#---"
2148 + "\n" + str(e)
2149 + "\n#---"
2150 + "\n" + repr(e)
2151 + "\n#---"
2152 )
2153 raise
2154
2155 except JSONDataSchemaError as e:
2156 print("SchemaError"
2157 + "\n" + str(e)
2158 + "\n#---"
2159 + "\n" + str(dir(e))
2160 + "\n#---"
2161 + "\n" + str(e)
2162 + "\n#---"
2163 + "\n" + repr(e)
2164 + "\n#---"
2165 + "\n" + "path:" + str(e.path)
2166 + "\n" + "schema_path:" + str(e.schema_path)
2167 + "\n#---"
2168 )
2169 raise
2170
2171 elif validator == MS_DRAFT3:
2172 if self.verbose:
2173 print("VERB:Validate: draft3")
2174 jsonschema.Draft3Validator(data, schema)
2175
2176 elif validator != MS_OFF:
2177 raise JSONDataValueError("unknown", "validator", str(validator))
2178
2179
2180 from jsondata.jsonpointer import JSONPointer
2181
2182