Package jsondata ::
Module JSONData
|
|
1
2 """Core features for the processing of structured JSON based in-memory data.
3 This comprises the load of a master model from a JSON file, and the
4 incremental addition and removal of branches by loading additional
5 JSON modules into the master model.
6 The resulting data could be saved for later reuse, where complex configuration
7 is varied by user interaction.
8 The implementation is based on the standard packages 'json' and 'jsonschema'.
9
10 This module uses for the syntax of JSON data either a preloaded
11 module, or loads the standard module by default. Current supported
12 packages are:
13
14 - **json**: The standard json package of the Python distribution.
15
16 - **ujson**: 'Ultra-JSON', a wrapped C implementation with
17 high-performance conversion.
18
19 The current default module is 'json' for syntax processing,
20 the standard package 'jsonschema' for the optional validation.
21
22 """
23 __author__ = 'Arno-Can Uestuensoez'
24 __maintainer__ = 'Arno-Can Uestuensoez'
25 __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
26 __copyright__ = "Copyright (C) 2015-2016 Arno-Can Uestuensoez @Ingenieurbuero Arno-Can Uestuensoez"
27 __version__ = '0.2.14'
28 __uuid__='63b597d6-4ada-4880-9f99-f5e0961351fb'
29
30 import os,sys
31 version = '{0}.{1}'.format(*sys.version_info[:2])
32 if not version in ('2.6','2.7',):
33 raise Exception("Requires Python-2.6.* or higher")
34
35
36
37
38 import copy
39 from types import NoneType
40
41
42
43 if sys.modules.get('json'):
44 import json as myjson
45 elif sys.modules.get('ujson'):
46 import ujson as myjson
47 else:
48 import json as myjson
49
50
51 import jsonschema
52 from jsonschema import ValidationError,SchemaError
53
54 MODE_JSON_RFC4927 = 0
55 """The first JSON RFC. """
56
57 MODE_JSON_RF7951 = 2
58 """The JSON RFC by 'now'. """
59
60 MODE_JSON_ECMA264 = 10
61 """The first JSON EMCMA standard."""
62
63 MODE_POINTER_RFC6901 = 20
64 """JSONPointer first IETF RFC."""
65
66 MODE_PATCH_RFC6902 = 30
67 """JSONPatch first IETF RFC."""
68
69 MODE_SCHEMA_OFF = 40
70 """No validation."""
71
72 MODE_SCHEMA_DRAFT3 = 43
73 """The first supported JSONSchema IETF-Draft."""
74
75 MODE_SCHEMA_DRAFT4 = 44
76 """The current supported JSONSchema IETF-Draft."""
77
78 MODE_SCHEMA_ON = 44
79 """The current default, DRAFT4."""
80
81
82
83 MATCH_INSERT = 0
84 """for dicts"""
85
86 MATCH_NO = 1
87 """negates the whole set"""
88
89 MATCH_KEY = 2
90 """for dicts"""
91
92 MATCH_CHLDATTR = 3
93 """for dicts and lists"""
94
95 MATCH_INDEX = 4
96 """for lists"""
97
98 MATCH_MEM = 5
99 """for dicts(value) and lists"""
100
101 MATCH_NEW = 6
102 """If not present create a new, else ignore and keep present untouched."""
103
104 MATCH_PRESENT = 7
105 """Check all are present, else fails."""
106
107
108 _interactive = False
109
110
111 from JSONDataExceptions import JSONDataParameter,JSONDataException,JSONDataValue,JSONDataKeyError,JSONDataSourceFile,JSONDataTargetFile,JSONDataNodeType
117 """ Error ambiguity of provided parameters."""
118
120 if _interactive:
121 s="Ambiguious input for:\n "+str(requested)
122 for sx in sources:
123 s+="\n "+str(sx)
124 else:
125 s="Ambiguious input for:"+str(requested)
126 for sx in sources:
127 s+=":"+str(sx)
128 Exception.__init__(self,s)
129
131 return "JSONDataAmbiguity:"+self.s
132
134 """A wrapper for a 'list' representing a path pointer
135 at the method interfaces. Required due to possible
136 ambiguity with the other type of in-memory node.
137 """
138 pass
139
141 """ Representation of a JSON based object data tree.
142
143 This class provides for the handling of the in-memory data
144 by the main hooks 'data', and 'schema'. This includes generic
145 methods for the advanced management of arbitrary 'branches'
146 in extension to RCF6902, and additional methods strictly
147 compliant to RFC6902.
148
149 Due to the pure in-memory support and addressing by the enclosed
150 module JSONPointer for RFC6901 compliant addressing by in memory
151 caching, the JSONData may outperform designs based on
152 operation on the native JSON representation.
153
154 Attributes:
155 **data**: The data tree of JSON based objects provided
156 by the module 'json'.
157 **schema**: The validator for 'data' provided by
158 the module 'jsonschema'.
159
160 Common call parameters provided by the methods of this class are:
161 *targetnode := addressreference*
162 The target node of called method. The 'targetnode' in general
163 represents the target of the called method. In most cases this
164 has to be a reference to a container for the modification
165 and/or insertion of resulting elements. The methods require
166 the change of contained items, which involves the application
167 of a 'key' pointing to the hook in point of the reference
168 to the modification.
169
170 *key := key-value*
171 The hook-in point for references of modified entries within
172 the targetnode container. The following values are supported:
173
174 *sourcenode := addressreference*
175 The in-memory node address of the source branch for the method,
176 e.g. 'copy' or 'move' operation.
177
178 The address references supported in this class refer the resulting
179 in-memory representation of a pointer path. The target is a node
180 within a Python data representation as provided by the package
181 '**json**' and compatible packages, e.g. '**ujson**'. The supported input
182 syntax is one of the following interchangeable formats::
183
184 # The reference to a in-memory-node.
185 addressreference := (
186 nodereference
187 | addressreference-source
188 )
189
190 nodereference:= (
191 <in-memory>
192 | ''
193 )
194
195 <in-memory> := "Memory representation of a JSON node, a 'dict'
196 or a 'list'. The in-memory Python node reference has to be
197 located within the document, due to performance reasons this
198 is not verified by default.
199
200 The 'nodereference' could be converted from the
201 'addressreference-source' representation."
202
203 '' := "Represents the whole document in accordance to RFC6901.
204 Same as 'self.data'."
205
206 # The source of the syntax for the description of the reference
207 # pointer path to a node. This is applicable on paths to be created.
208 addressreference-source := (
209 JSONPointer
210 )
211
212 JSONPointer:="A JSONPointer object in accordance to RFC6901.
213 for additional information on input formats refer to the
214 class documentation.
215 This class provides a fully qualified path pointer, which
216 could be converted into any of the required representations."
217
218 For hooks by 'key-value' within addressed containers::
219
220 key-value:=(None|<list-index>|<dict-key>)
221
222 None := "When the 'key' parameter is 'None', the action
223 optionally could be based on the keys of the 'sourcenode'.
224 The contents of the branch replace the node contents
225 when the type of the branch matches the hook."
226
227 <list-index>:=('-'|int)
228
229 <dict-key>:="Valid for a 'dict' only, sets key/value pair,
230 where present is replace, new is created."
231
232 '-' := "Valid for a 'list' only, appends to present."
233
234 int := "Valid for a 'list' only, replaces present when
235 0 < #int < len(Node)."
236
237 In the parameter lists of methods used term 'pointer' is either
238 an object of class 'JSONPointer', or a list of pointer path
239 entries.
240
241 The JSON types 'object' and 'array' behave in Python slightly
242 different in accordance to RFC6902. The main difference arise
243 from the restrictions on applicable key values. Whereas the
244 ranges are limited logically by the actual container sizes,
245 the object types provide free and unlimited keys. The limit
246 is set by type restriction to unicode and 'non-nil' only
247 for keys.
248
249 """
251 """Loads and validates a JSON definition with the corresponding schema file.
252
253 Args:
254 args*: Optional position parameters, these branch_replace corresponding key
255 parameters.
256 data
257
258 **kargs:
259 data: JSON data within memory.
260
261 default:= None
262 indent_str: Defied the indentation of 'str'.
263
264 default:= 4
265 interactive: Hints on command line call for optional change of display format.
266
267 default:= False
268 schema: A valid in-meory JSONschema.
269
270 default:= None
271 validator: [default, draft3, draft4, on, off, ]
272 Sets schema validator for the data file.
273 The values are: default=validate, draft3=Draft3Validator,
274 off=None
275
276 default:= off
277
278 printdata: branch=None
279 Pretty print resulting final data of branch.
280
281 default:= top
282 printschema: branch=None
283 Pretty print resulting schema.
284
285 default:= top
286
287 debug: Displays extended state data for developers.
288 Requires __debug__==True.
289 verbose: Extends the amount of the display of
290 processing data.
291
292 Returns:
293 Results in an initialized object.
294
295 Raises:
296 NameError:
297
298 JSONDataValue:
299
300 jsonschema.ValidationError:
301
302 jsonschema.SchemaError:
303
304 """
305
306
307
308 kimp={}
309
310
311 self.mode_json = MODE_JSON_RF7951
312 self.mode_schema = MODE_SCHEMA_DRAFT4
313 self.mode_pointer = MODE_POINTER_RFC6901
314 self.mode_patch = MODE_PATCH_RFC6902
315
316 self.branch = None
317 self.data = None
318 self.schema = None
319 self.indent = 4
320 self.sort_keys = False
321 self.validator = MODE_SCHEMA_OFF
322
323 if __debug__:
324 self.debug = False
325 self.verbose = False
326
327
328 global _interactive
329 _interactive = kargs.get('interactive',False)
330
331
332 self.schema = kargs.get('schema',None)
333
334
335 if args:
336 for i in range(0,len(args)):
337 if i == 0:
338 self.data = args[i]
339
340
341
342
343 for k,v in kargs.items():
344
345
346 if k == 'data':
347 self.data = v
348 elif k == 'indent_str':
349 self.indent_str = v
350 elif k == 'loadcached':
351 self.loadcached = v
352 elif k == 'requires':
353 self.requires = v
354 elif k == 'validator':
355 if v == 'default' or v == MODE_SCHEMA_DRAFT4:
356 self.validator = MODE_SCHEMA_DRAFT4
357 elif v == 'draft3' or v == MODE_SCHEMA_DRAFT3:
358 self.validator = MODE_SCHEMA_DRAFT3
359 elif v == 'off' or v == MODE_SCHEMA_OFF:
360 self.validator = MODE_SCHEMA_OFF
361 else:
362 raise JSONDataValue("unknown",k,str(v))
363 elif k == 'verbose':
364 self.verbose = v
365 elif __debug__:
366 if k == 'debug':
367 self.debug = v
368 elif k == 'interactive':
369 _interactive = v
370
371
372 if self.verbose:
373 print "VERB:JSON= "+str(myjson.__name__)+" / "+str(myjson.__version__)
374 if __debug__:
375 if self.debug:
376 print "DBG:JSON= "+str(myjson.__name__)+" / "+str(myjson.__version__)
377 print "DBG:self.data= #["+str(self.schemafile)+"]#"
378 print "DBG:self.schema= #["+str(self.schema)+"]#"
379
380
381
382 if type(self.data) is NoneType:
383 raise JSONDataParameter("value","data",str(self.data))
384
385
386 if not self.schema and self.validator != MODE_SCHEMA_OFF:
387 raise JSONDataParameter("value","schema",str(self.schema))
388
389
390 if self.validator != MODE_SCHEMA_OFF:
391 self.validate(self.data,self.schema,self.validator)
392
393 if __debug__:
394 if self.debug:
395 print "DBG:self.pathlist= "+str(self.pathlist)
396 print "DBG:self.filelist= "+str(self.filelist)
397 print "DBG:self.filepathlist="+str(self.filepathlist)
398 print "DBG:self.schemafile= "+str(self.schemafile)
399 print "DBG:self.schema= #["+str(self.schema)+"]#"
400
402 """Adds the structure 'x' to 'self', performs deep-operation.
403 """
404 return self
405
407 """Gets the intersection of 'x' and 'self', performs deep-operation.
408 """
409 return self
410
412 """Evaluates the pointed value from the document.
413
414 Args:
415 x: A valid JSONPointer.
416
417 Returns:
418 The pointed value, or None.
419
420 Raises:
421 JSONPointerException
422 """
423 if isinstance(x,JSONPointer):
424 return x.get_node_or_value(self.data)
425 return JSONPointer(x).get_node_or_value(self.data)
426
428 """Compares this JSONData.data with x.
429
430 Args:
431 x: A valid JSONData.
432
433 Returns:
434 True or False
435
436 Raises:
437 JSONDataException
438 """
439 if not self.data and not x :
440 return True
441 return JSONData.getTreeDiff(self.data, x)
442
444 """Adds the structure 'x' to 'self', performs deep-operation.
445 """
446 return self
447
449 """Gets the intersection of 'x' and 'self', performs deep-operation.
450 """
451 return self
452
454 """Returns the difference-modulo-set.
455 """
456 return self
457
459 """Duplicates the elements of 'self' 'x' times.
460 """
461 return self
462
464 """Duplicates the elements of 'self' 'x' times.
465
466 The operations::
467
468 z = S * x
469
470 Returns the remaining subset of:
471
472 z = S - 1 * x
473
474 where '1*x' is for each present element of 'x'. When multiple exist
475 'n-1' remain.
476
477 """
478
479 return self
480
482 """Returns the superset of branches and attributes.
483 """
484 return self
485
487 """Returns the residue of X after each present element of 'x' is removed.
488 """
489 return self
490
492 """Returns the elements present in one only.
493 """
494 return self
495
497 """Returns the difference-modulo-set.
498
499 The operations::
500
501 z = S % x
502
503 Returns the remaining subset of:
504
505 z = S - n * x
506
507 where 'n*x' is the maximum number of present branches 'x'. When
508 multiple exist, all matching are removed.
509
510 """
511 return self
512
514 """Adds the structure 'x' to 'self', performs deep-operation.
515 """
516 return self
517
519 """Gets the intersection of 'x' and 'self', performs deep-operation.
520 """
521 return self
522
524 """Returns the difference-modulo-set.
525 """
526 return self
527
529 """Duplicates the elements of 'self' 'x' times.
530 """
531 return self
532
534 """Returns the superset of branches and attributes.
535 """
536 return self
537
539 """Returns the superset of branches and attributes.
540 """
541 return self
542
544 """Returns the residue of X after each present element of 'x' is removed.
545 """
546 return self
547
549 """Returns the elements present in one only.
550 """
551 return self
552
554 """Returns the residue of X after each present element of 'x' is removed.
555
556 The operations::
557
558 z = S - x
559
560 Returns the remaining subset of:
561
562 z = S - 1 * x
563
564 where '1*x' is for each present element of 'x'. When multiple exist
565 'n-1' remain.
566
567 """
568
569 return self
570
572 """Returns the structure elements present in in one only.
573 """
574
575 return self
576
578 """Dump data.
579 """
580
581
582
583 return repr(self.data)
584
585
587 """Dumps data by pretty print.
588 """
589 return myjson.dumps(self.data, indent=self.indent, sort_keys=self.sort_keys)
590
592 """Support of slices, for 'iterator' refer to self.__iter__.
593 """
594
595
596
597
598 if not self.data:
599 return None
600 return self.data[key]
601
603 """Provides an iterator for data.
604 """
605 return iter(self.data)
606
608 """Compares this JSONData with x.
609
610 Args:
611 x: A valid JSONData.
612
613 Returns:
614 True or False
615
616 Raises:
617 JSONDataException
618 """
619 return not self.__eq__(x)
620
621 - def branch_add(self, targetnode, key, sourcenode):
622 """Add a complete branch into a target structure of type object.
623
624 Present previous branches are replaced, non-existent branches are
625 added. The added branch is created by a deep copy, thus is completely
626 independent from the source.
627
628 Call: *branch_add* ( **t**, **k**, **s** )
629
630 +---+------------------+---------+----------------+-----------+
631 | i | target | source | add | |
632 | +----------+-------+---------+-------+--------+ |
633 | | t | k | s | from | to | type |
634 +===+==========+=======+=========+=======+========+===========+
635 | 0 | node | key | node | s | t[k] | any |
636 +---+----------+-------+---------+-------+--------+-----------+
637 | 1 | node | None | node | s | t[*] | match |
638 +---+----------+-------+---------+-------+--------+-----------+
639
640 0. Use-Case-0: Any source node type is added as 't[k]'.
641
642 1. Use-Case-1: The content keys of node 's' are added each
643 to the node 't'. Therefore the node types of 's' and 't'
644 have to match.
645
646 This behaviour is defined in respect to the parameter
647 passing of Python.
648
649 Args:
650 targetnode := nodereference
651 Target container node where the branch is to be inserted.
652
653 key := key-value
654 Hook for the insertion within target node.
655
656 sourcenode := nodereference
657 Source branch to be inserted into the target tree.
658
659 Returns:
660 When successful returns 'True', else returns either 'False', or
661 raises an exception.
662
663 Raises:
664 JSONDataNodeType:
665 JSONDataKeyError:
666
667 """
668 ret = False
669 if isinstance(targetnode,JSONPointer):
670 try:
671 if not key:
672 targetnode,key = targetnode.get_node_and_child(self.data)
673 else:
674 targetnode = targetnode.get_node(self.data,False)
675 except:
676
677 self.branch_create('',targetnode)
678 if not key:
679 targetnode,key = targetnode.get_node_and_child(self.data)
680 else:
681 targetnode = targetnode.get_node(self.data,True)
682
683 if type(targetnode) == dict:
684 if key:
685 targetnode[key] = copy.deepcopy(sourcenode)
686 else:
687 if type(sourcenode) != dict:
688 raise JSONDataNodeType("type","targetnode/sourcenode",type(targetnode)+"/"+type(sourcenode))
689 targetnode.clear()
690 for k,v in sourcenode.items():
691 targetnode[k]=copy.deepcopy(v)
692 return True
693
694 elif type(targetnode) == list:
695 if key == '-':
696 targetnode.append(copy.deepcopy(sourcenode))
697 ret = True
698 elif 0 <= key < len(targetnode):
699 targetnode[key] = copy.deepcopy(sourcenode)
700 elif type(key) is NoneType:
701 if type(sourcenode) != list:
702 raise JSONDataNodeType("node/keys != type:does not match:",targetnode, sourcenode)
703 for k in range(0,len(targetnode)):
704 targetnode.pop()
705 for v in sourcenode:
706 targetnode.append(copy.deepcopy(v))
707 else:
708 raise JSONDataKeyError("mismatch:node:type", 'key', key, 'key-type', type(key),'node-type',type(targetnode))
709 return True
710
711 else:
712 raise JSONDataNodeType("type","targetnode/sourcenode",str(type(targetnode))+"/"+str(type(sourcenode)))
713
714 return ret
715
716 - def branch_copy(self, targetnode, key, sourcenode, force=True):
717 """Copies the source branch to the target node.
718
719 The copy is internally mapped onto the 'branch_add' call,
720 thus shares basically the same parameters and behaviour.
721 Due to the required modification of the target only, the
722 copy is slightly different from the 'branch_move' call.
723
724 Call: *branch_copy* ( **t**, **k**, **s** )
725
726 +---+------------------+---------+----------------+-----------+
727 | i | target | source | copy | |
728 | +----------+-------+---------+-------+--------+ |
729 | | t | k | s | from | to | type |
730 +===+==========+=======+=========+=======+========+===========+
731 | 0 | node | key | node | s | t[k] | any |
732 +---+----------+-------+---------+-------+--------+-----------+
733 | 1 | node | None | node | s | t[sk] | match |
734 +---+----------+-------+---------+-------+--------+-----------+
735
736 For the description of the Use-Cases refer to branch_add.
737
738 Args:
739 targetnode := nodereference
740 Target tree the branch is to be inserted.
741
742 key := key-value
743 Key of insertion point within target node.
744
745 sourcenode := nodereference
746 Source branch to be inserted into target tree.
747
748 force: If true present are replaced, else only non-present
749 are copied.
750
751 default:=True
752
753 Returns:
754 When successful returns 'True', else returns either 'False', or
755 raises an exception.
756
757 Raises:
758 JSONData:
759
760 """
761 if force:
762 return self.branch_add(targetnode, key, sourcenode)
763 elif self.isApplicable(targetnode, key, sourcenode, [MATCH_NEW]):
764 return self.branch_add(targetnode, key, sourcenode)
765 else:
766 return False
767
769 """Creates a branch located at targetnode.
770
771 The requested branch as created as child value of provided
772 'targetnode'. 'targetnode' is required to exist.
773
774 **REMARK**: Current version relies for the created nodes on the
775 content type of the key(str,unicode)/index(int), later
776 versions may use a provided schema.
777
778 Call: *branch_create* ( **t**, **b**, **v** )
779
780 +---+----------+---------+-------+
781 | i | target | branch | value |
782 | +----------+---------+-------+
783 | | t | b | v |
784 +===+==========+=========+=======+
785 | 0 | node | list | [any] |
786 +---+----------+---------+-------+
787 | 1 | node | list | [any] |
788 +---+----------+---------+-------+
789 | 2 | node | pointer | [any] |
790 +---+----------+---------+-------+
791 | 3 | node | pointer | [any] |
792 +---+----------+---------+-------+
793
794
795 Args:
796 targetnode := nodereference
797 Base node for the insertion of branch.
798
799 branch := addressreference-source
800 New branch to be created in the target tree.
801 A Pointer address path relative to the 'targetnode'.
802
803 value: Optional value for the leaf. The value itselfn
804 could be either an atomic type, or a branch itself
805 in accordance to RFC6902.
806
807 Returns:
808 When successful returns the leaf node, else returns either
809 'None', or raises an exception.
810
811 Raises:
812 JSONData:
813
814 """
815 ret = None
816
817 def getNewNode(keytype):
818 """Fetch the required new container."""
819 if keytype == '-':
820 return []
821 elif type(keytype) is int:
822 return []
823 elif type(keytype) in ( str, unicode, ):
824 return {}
825 elif not keytype:
826 return None
827 else:
828 raise JSONDataKeyError("type",'keytype',str(keytype))
829
830 if isinstance(branch,JSONPointer):
831
832
833 branch = branch.get_path_list()
834
835 if not type(branch) is list:
836 raise JSONDataException("value","branch",branch)
837
838 if targetnode == '':
839 targetnode = self.data
840
841 if type(targetnode) == dict:
842
843 if type(branch[0]) not in (str,unicode,):
844 raise JSONDataException("value","container/branch",str(type(targetnode))+"/"+str(type(branch[0])))
845
846 if len(branch)>1:
847 if not targetnode.get(unicode(branch[0]),False):
848 targetnode[unicode(branch[0])] = getNewNode(branch[1])
849 ret = self.branch_create(targetnode[branch[0]], branch[1:], value)
850 else:
851 if targetnode.get(branch[0],False):
852 raise JSONDataException("exists","branch",str(branch[0]))
853 ret = targetnode[unicode(branch[0])] = self.getCanonical(value)
854
855 elif type(targetnode) == list:
856 if type(branch[0]) in (int,) and branch[0] < len(targetnode):
857 raise JSONDataException("exists","branch",str(branch[0]))
858 elif unicode(branch[0]) == u'-':
859 pass
860 else:
861 raise JSONDataException("value","targetnode/branch:"+str(type(targetnode))+"/"+str(type(branch[0])))
862
863 if len(branch) == 1:
864 if branch[0] == '-':
865 branch[0] = len(targetnode)
866 targetnode.append(self.getCanonical(value))
867 else:
868 targetnode[branch[0]] = self.getCanonical(value)
869 ret = targetnode
870 else:
871 if branch[0] == '-':
872 branch[0] = len(targetnode)
873 targetnode.append(getNewNode(branch[1]))
874 ret = self.branch_create(targetnode[branch[0]], branch[1:], value)
875
876 else:
877 raise JSONDataException("type","targetnode",str(type(targetnode)))
878
879 return ret
880
881 - def branch_move(self, targetnode, key, sourcenode, skey, force=True, forcext=False):
882 """Moves a source branch to target node.
883
884 Moves by default only when target is not yet present. The
885 parameters for 'list', 'force' enabled to overwrite, whereas
886 the parameter 'forcext' enables to move all entries and
887 extend the target items.
888
889 Due to the Python specific passing of flat parameters as
890 a copy of the reference without access to the actual source
891 entry, these are slightly different from the 'branch_copy'
892 and 'branch_add' methods modifying the target only. Therefore
893 additional source keys 'skey' are required by 'move' in order
894 to enable the modification of the source entry.
895
896 Call: *branch_move* ( **t**, **k**, **s**, **sk** )
897
898 +---+------------------+-----------------+---------------+-------+
899 | i | target | source | move | |
900 | +----------+-------+---------+-------+-------+-------+ |
901 | | t | k | s | sk | from | to | type |
902 +===+==========+=======+=========+=======+=======+=======+=======+
903 | 0 | node | key | node | key | s[sk] | t[k] | any |
904 +---+----------+-------+---------+-------+-------+-------+-------+
905 | 1 | node | None | node | key | s[sk] | t[sk] | match |
906 +---+----------+-------+---------+-------+-------+-------+-------+
907
908 0. Use-Case-0: Moves any.
909
910 1. Use-Case-1: Moves matching key types only: list-to-list,
911 or dict-to-dict.
912
913 Args:
914 targetnode := nodereference
915 Target tree the branch is to be inserted.
916
917 key := key-value
918 Key of insertion point within target node.
919
920 sourcenode := nodereference
921 Source branch to be inserted into target tree.
922
923 skey := key-value
924 Key of the source to be moved to target node.
925
926 force: If true present are replaced, else only
927 non-present are moved.
928
929 default:=True
930
931 forcext: If true target size will be extended when
932 required. This is applicable on 'list' only, and
933 extends RFC6902. The same effect is given for
934 a 'list' by one of:
935
936 * key:='-'
937
938 * key:=None and skey:='-'
939
940 Returns:
941 When successful returns 'True', else returns either
942 'False', or raises an exception.
943
944 Raises:
945 JSONData:
946 JSONDataKey:
947 KeyError:
948
949 """
950 ret = False
951
952 if type(targetnode) is dict:
953
954 if type(skey) is NoneType:
955 if type(key) is NoneType:
956 raise JSONDataKeyError("missing","key",str(key))
957
958 else:
959 targetnode[key] = sourcenode[key]
960
961 else:
962 if type(key) is NoneType:
963 if targetnode.get(skey):
964 if not force:
965 raise JSONDataKeyError("present","skey",str(skey))
966 targetnode[skey] = sourcenode[skey]
967 else:
968 if targetnode.get(key):
969 if not force:
970 raise JSONDataKeyError("present","key",str(key))
971 targetnode[key] = sourcenode[skey]
972
973 sourcenode.pop(skey)
974 ret = True
975
976 elif type(targetnode) is list:
977
978 if type(skey) is NoneType:
979 if type(key) is NoneType:
980 raise JSONDataKeyError("missing","key",str(key))
981
982 elif key == '-':
983 if type(sourcenode) is list:
984 for v in reversed(sourcenode):
985 targetnode.append(v)
986 sourcenode.pop()
987 else:
988 raise JSONDataKeyError("type/dict","key",str(key))
989
990 elif key < len(sourcenode):
991 targetnode[key] = sourcenode[key]
992 sourcenode.pop(key)
993
994 else:
995 raise JSONDataKeyError("key",str(key))
996
997 elif skey == '-':
998 raise JSONDataKeyError("type","skey",str(skey))
999
1000 else:
1001 if type(key) is NoneType:
1002 if skey < len(targetnode):
1003 if force:
1004 targetnode[skey] = sourcenode[skey]
1005 else:
1006 raise JSONDataKeyError("present","skey",str(skey))
1007 elif forcext:
1008 targetnode.append(sourcenode[skey])
1009 else:
1010 raise JSONDataKeyError("value","skey",str(skey))
1011 else:
1012 if type(key) is int and type(skey) is int and skey < len(sourcenode):
1013 if key < len(targetnode):
1014 if force:
1015 targetnode[key] = sourcenode[skey]
1016 else:
1017 raise JSONDataKeyError("present","key",str(key))
1018 elif forcext:
1019 targetnode.append(sourcenode[skey])
1020
1021 elif key == '-':
1022 targetnode.append(sourcenode[skey])
1023 else:
1024 raise JSONDataKeyError("value","skey",str(skey))
1025 sourcenode.pop(skey)
1026
1027 ret = True
1028
1029
1030 if not ret:
1031 raise JSONDataException("type","targetnode",str(type(targetnode)))
1032
1033 return ret
1034
1036 """Removes a branch from a target structure.
1037
1038 The corresponding elements of the 'targetnode' tree are removed.
1039 The remaining are kept untouched. For tree nodes as leafs the whole
1040 corresponding subtree is deleted.
1041
1042 REMARK: No reference checks are done, so the user is responsible
1043 for additional references.
1044
1045 Call: *branch_remove* ( **t**, **k** )
1046
1047 +---+------------------+--------+-------+
1048 | i | target | remove | |
1049 | +----------+-------+--------+ |
1050 | | t | k | branch | type |
1051 +===+==========+=======+========+=======+
1052 | 0 | node | key | t[k] | any |
1053 +---+----------+-------+--------+-------+
1054 | 1 | node | None | t[*] | any |
1055 +---+----------+-------+--------+-------+
1056
1057 0. Use-Case-0: Removes any type of node.
1058
1059 1. Use-Case-1: Removes all contained items of any type.
1060
1061 Args:
1062 targetnode := nodereference
1063 Container of 'targetnode' with items to be removed.
1064
1065 key := key-value
1066 The item to be removed from the 'targetnode'.
1067 When 'None', all contained items are removed.
1068
1069 Returns:
1070 When successful returns 'True', else returns either 'False', or
1071 raises an exception.
1072
1073 Raises:
1074 JSONDataException:
1075
1076 """
1077 ret = False
1078
1079 if type(targetnode) == dict:
1080 if not key:
1081 targetnode.clear()
1082 else:
1083 targetnode.pop(key)
1084 ret = True
1085
1086 elif type(targetnode) == list:
1087 if type(key) is NoneType:
1088 [targetnode.pop() for l in range(0,len(targetnode))]
1089 else:
1090 targetnode.pop(key)
1091 ret = True
1092
1093 if not ret:
1094 raise JSONDataException("type","targetnode",str(targetnode))
1095
1096 return ret
1097
1099 """Replaces the value of the target node by the copy of the source branch.
1100
1101 Requires in order to RFC6902, all items to be replaced has to be
1102 present. Thus fails if at least one is missing.
1103
1104 Internally the 'branch_add()' call is used with a deep copy.
1105 When a swallow copy is required the 'branch_move()' has to be used.
1106
1107 Args:
1108 targetnode := nodereference
1109 Target tree the branch is to be inserted.
1110
1111 key := key-value
1112 Key of insertion point within target node.
1113 If key==None, the whole set of keys is replaced by
1114 the content of the 'sourcenode'.
1115
1116 sourcenode := nodereference
1117 Source branch to be inserted into target tree.
1118
1119 force: If true present are replaced, else only non-present
1120 are copied.
1121
1122 Returns:
1123 When successful returns 'True', else returns either 'False', or
1124 raises an exception.
1125
1126 Raises:
1127 JSONData:
1128
1129 """
1130 if not self.isApplicable(targetnode, key, sourcenode, [MATCH_PRESENT]):
1131 return False
1132 return self.branch_add(targetnode, key, sourcenode)
1133
1134 @classmethod
1136 """Tests match in accordance to RFC6902.
1137
1138 Args:
1139 targetnode := a valid node
1140 Node to be compared with the value. Due to
1141 ambiguity the automated conversion is not
1142 reliable, thus it has to be valid.
1143
1144 value: Expected value for the given node.
1145
1146 Returns:
1147 When successful returns 'True', else returns 'False'.
1148
1149 Raises:
1150 JSONData:
1151 """
1152 if not targetnode and not value :
1153 return True
1154 return cls.getTreeDiff(targetnode, value)
1155
1157 """Returns the reference to data."""
1158 return self.data
1159
1161 """Returns the reference to schema."""
1162 return self.schema
1163
1164 @classmethod
1165 - def getTreeDiff(cls, n0, n1, difflst=None, alldifs=False, dl=0, path=''):
1166 """Recursive tree compare for Python trees as used for the package 'json'.
1167
1168 Finds diff in native Python trees assembled by the standard package 'json'
1169 and compatible, e.g. 'ujson'.
1170 """
1171
1172 if type(n0) is str:
1173 n0 = unicode(n0)
1174 if type(n1) is str:
1175 n1 = unicode(n1)
1176 if type(n0) != type(n1):
1177 if type(difflst) != NoneType:
1178 difflst.append({'n0'+path:n0,'n1'+path:n1,'dl':dl})
1179 return False
1180
1181 if type(n0) is list:
1182 if len(n0) != len(n1):
1183 if type(difflst) != NoneType:
1184 difflst.append({'n0'+path:n0,'n1'+path:n1,'dl':dl})
1185 return False
1186
1187 for ni in range(0,len(n0)):
1188 if type(n0[ni]) in (list,dict):
1189 if not cls.getTreeDiff(n0[ni],n1[ni],difflst,alldifs,dl+1,path+'['+str(ni)+']'):
1190 if not alldifs:
1191 return False
1192 elif n0[ni] != n1[ni]:
1193 if type(difflst) != NoneType:
1194 _path = path + '['+str(ni)+']'
1195 difflst.append({'n0'+_path:n0[ni],'n1'+_path:n1[ni],'dl':dl})
1196 if not alldifs:
1197 return False
1198
1199 elif type(n0) is dict:
1200 if len(n0.keys()) != len(n1.keys()):
1201 if type(difflst) != NoneType:
1202 difflst.append({'n0'+path:n0,'n1'+path:n1,'dl':dl})
1203 return False
1204
1205 for ni,v in n0.items():
1206 if n1.get(ni):
1207 if type(v) in (list,dict):
1208 if not cls.getTreeDiff(v,n1[ni],difflst,alldifs,dl+1,path+'['+str(ni)+']'):
1209 if not alldifs:
1210 return False
1211 else:
1212 if v != n1[ni]:
1213 if type(difflst) != NoneType:
1214 _path = path + '['+str(ni)+']'
1215 difflst.append({ 'n0'+_path:n0[ni],'n1'+_path:n1[ni],'dl':dl})
1216 if not alldifs:
1217 return False
1218 else:
1219 if type(difflst) != NoneType:
1220 _path = path +'['+str(ni)+']'
1221 difflst.append({'n0'+_path:n0[ni],'n1'+path:n1,'dl':dl})
1222 if not alldifs:
1223 return False
1224
1225 else:
1226 if n0 == n1:
1227 return True
1228 if type(difflst) != NoneType:
1229 difflst.append({'n0'+path:n0,'n1'+path:n1,'dl':dl})
1230 return False
1231 if type(difflst) != NoneType:
1232 return len(difflst) == 0
1233 return True
1234
1235 FIRST = 1
1236 """First match only."""
1237
1238 ALL = 3
1239 """All matches."""
1240
1241 @classmethod
1243 """Converts a node address into the corresponding pointer path.
1244
1245 The current implementation is search based, thus may have
1246 performance issues when frequently applied.
1247
1248 Args:
1249 node: Address of Node to be searched for.
1250
1251 base: A tree top nodes to search for node.
1252
1253 restype: Type of search.
1254
1255 first: The first match only.
1256
1257 all: All matches.
1258
1259 Returns:
1260 Returns a list of lists, where the contained lists are pointer
1261 path-lists for matched elements.
1262
1263 * restype:=FIRST: '[[<first-match>]]',
1264
1265 * restype:=ALL: '[[<first-match>],[<second-match>],...]'
1266
1267 Raises:
1268 JSONData:
1269 """
1270 if not node or not base:
1271 return []
1272
1273 spath = []
1274 res = []
1275
1276 kl = 0
1277 kd = None
1278
1279
1280 if type(base) is list:
1281 kl = 0
1282 if id(node) == id(base):
1283 res.append([kl])
1284 else:
1285 for sx in base:
1286 if id(node) == id(sx):
1287 s = spath[:]
1288 s.append(kl)
1289 res.append(s)
1290
1291 elif type(sx) in (dict,list):
1292 sublst = cls.getPointerPath(node,sx,restype)
1293 if sublst:
1294 for slx in sublst:
1295 s = spath[:]
1296 s.append(kl)
1297 s.extend(slx)
1298 res.append(s)
1299 kl += 1
1300
1301 elif type(base) is dict:
1302 if id(node) == id(base):
1303 res.append([''])
1304 else:
1305 for k,v in base.items():
1306 if id(node) == id(v):
1307 spath.append(k)
1308 res.append(spath)
1309 continue
1310 elif type(v) in (list,dict):
1311 sublst = cls.getPointerPath(node,v,restype)
1312 if sublst:
1313 for slx in sublst:
1314 if slx:
1315 s = spath[:]
1316 s.append(k)
1317 s.extend(slx)
1318 res.append(s)
1319
1320
1321 if res and restype == JSONData.FIRST:
1322 return [res[0]]
1323 return res
1324
1326 """Fetches the canonical value.
1327
1328 The actual value could be either an atomic value, a node
1329 representing a branch, or a reference to an atomic value.
1330
1331 Args:
1332 value: Value pointer to be evaluated to the actual value.
1333 Valid input types are:
1334 int,str,unicode: Integer, kept as an atomic integer
1335 value.
1336 dict,list: Assumed to be a valid node for 'json'
1337 package, used by reference.
1338
1339 JSONPointer: A JSON pointer in accordance to
1340 RFC6901.
1341
1342 Returns:
1343 When successful returns the value, else returns either 'False', or
1344 raises an exception.
1345
1346 Raises:
1347 JSONData:
1348
1349 """
1350 if type(value) in (dict,list):
1351 return value
1352 elif type(value) in ( int, float, ):
1353 return value
1354 elif type(value) in ( str, unicode, ):
1355 return unicode(value)
1356 elif isinstance(value,JSONPointer):
1357 return value.get_node_or_value(self.data)
1358 elif not value:
1359 return None
1360 else:
1361 raise JSONDataException("type","value",str(value))
1362
1363 - def isApplicable(self, targetnode, key, branch, matchcondition=None, **kargs):
1364 """ Checks applicability by validation of provided match criteria.
1365
1366 The contained data in 'datafile' could be either the initial data
1367 tree, or a new branch defined by a fresh tree structure. The
1368 'targetnode' defines the parent container where the new branch has
1369 to be hooked-in.
1370
1371 Args:
1372 targetnode:
1373 Target container hook for the inclusion of the loaded branch.
1374 The branch is treated as a child-branch, hooked into the
1375 provided container 'targetnode'.
1376 branch:
1377 Branch to be imported into the target container. The branch
1378 is treated as a child-branch.
1379 matchcondition:
1380 Defines the criteria for comparison of present child nodes
1381 in the target container. The value is a list of critarias
1382 combined by logical AND. The criteria may vary due to
1383 the requirement and the type of applied container:
1384 - common: Common provided criteria are:
1385 - insert: Just checks whether the branch could be inserted.
1386 In case of 'list' by 'append', in case of a 'dict' by
1387 the insert-[]-operator.
1388 This is in particular foreseen for the initial creation
1389 of new nodes.
1390 - present: Checks whether all are present.
1391 - no: Inverts the match criteria for the whole current set.
1392 - dict: The provided criteria are:
1393 - key: Both share the same key(s).
1394 - child_attr_list: A list of child attributes to be matched.
1395 This may assure e.g. compatibility by a user defined ID,
1396 and or a UUID.
1397
1398 default:=['key',]
1399 - list: The provided criteria are:
1400 - index: The positions of source and target have to match.
1401 - child_attr_list: A list of child attributes to be matched,
1402 thus e.g. the 'key' of dictionaries could be emulated
1403 by an arbitrary attribute like 'mykey'.
1404 This may assure e.g. compatibility by a user defined ID,
1405 and or a UUID.
1406 - mem: Checks whether the in-memory element is already present.
1407 Even though this is a quite weak criteria, it is probably
1408 the only and one common generic criteria for lists.
1409
1410 default:= mem # ATTENTION: almost any call adds a branch!
1411 **kargs:
1412 childattrlist: A list of user defined child attributes which
1413 all together(AND) define the match criteria.
1414
1415 default:=None, returns 'True'
1416 Returns:
1417 When successful returns 'True', else returns either 'False', or
1418 raises an exception.
1419
1420 The rule of thumb is:
1421 - type-mismatch: Exception
1422 - value-mismatch: return False
1423
1424 Success is: no-defined-condition or no-failing-condition
1425
1426 Raises:
1427 JSONData:
1428
1429 JSONDataValue:
1430
1431 """
1432
1433
1434
1435
1436 if not matchcondition:
1437 return True
1438 childattrlist = None
1439 _matchcondition = []
1440 for v in matchcondition:
1441
1442 if v == 'key' or v == MATCH_KEY:
1443 _matchcondition.append(MATCH_KEY)
1444 elif v == 'no' or v == MATCH_NO:
1445 _matchcondition.append(MATCH_NO)
1446 elif v == 'child_attr_list' or v == MATCH_CHLDATTR:
1447 _matchcondition.append(MATCH_CHLDATTR)
1448 elif v == 'index' or v == MATCH_INDEX:
1449 _matchcondition.append(MATCH_INDEX)
1450 elif v == 'mem' or v == MATCH_MEM:
1451 _matchcondition.append(MATCH_MEM)
1452 elif v == 'new' or v == MATCH_NEW:
1453 _matchcondition.append(MATCH_NEW)
1454 elif v == 'present' or v == MATCH_PRESENT:
1455 _matchcondition.append(MATCH_PRESENT)
1456 else:
1457 raise JSONDataValue("value","matchcondition",str(v))
1458 for k,v in kargs.items():
1459 if k == 'childattrlist':
1460 childattrlist = v
1461
1462
1463
1464
1465 retOK = True
1466
1467 if isinstance(targetnode, JSONData):
1468 targetnode = targetnode.data
1469 if isinstance(branch, JSONData):
1470 branch = branch.data
1471
1472
1473
1474 if type(key) is NoneType and type(targetnode) != type(branch):
1475 raise JSONDataException("type","targetnode",str(type(targetnode)))
1476
1477
1478 if not _matchcondition:
1479 _matchcondition = [MATCH_INSERT]
1480
1481 if MATCH_NO in _matchcondition:
1482 retFailed = True
1483 retOK = False
1484 else:
1485 retFailed = False
1486 retOK = True
1487
1488 for m in _matchcondition:
1489 if m == MATCH_NO:
1490 continue
1491 elif m == MATCH_INSERT:
1492 if not type(targetnode) in (dict,list):
1493 raise JSONDataException("type","targetnode",str(type(targetnode)))
1494 elif m == MATCH_KEY:
1495 if type(targetnode) != dict:
1496 raise JSONDataException("type","targetnode",str(type(targetnode)))
1497 for k in branch.keys():
1498 if not targetnode.get(k):
1499 return retFailed
1500 elif m == MATCH_CHLDATTR:
1501 if not type(targetnode) in (list,dict):
1502 raise JSONDataException("type","targetnode",str(type(targetnode)))
1503 if childattrlist != None:
1504 if type(branch) == dict:
1505 for ca in childattrlist:
1506 if not targetnode.get(ca):
1507 return retFailed
1508 elif type(branch) == list:
1509 for l in targetnode:
1510 if not type(l) is dict:
1511 raise JSONDataException("type","targetnode",str(type(targetnode)))
1512 for ca in childattrlist:
1513 if not type(ca) is dict:
1514 raise JSONDataException("type","targetnode",str(type(targetnode)))
1515 if not l.get(ca):
1516 return retFailed
1517 else:
1518 raise JSONDataException("type","targetnode",str(type(targetnode)))
1519 elif m == MATCH_INDEX:
1520 if type(targetnode) != list:
1521 raise JSONDataException("type","targetnode",str(type(targetnode)))
1522 if len(targetnode) > len(branch):
1523 return retFailed
1524 elif m == MATCH_NEW:
1525 if type(targetnode) == list:
1526 if key == '-':
1527 pass
1528 elif not type(key) is NoneType:
1529 if 0 <= key < len(targetnode):
1530 if targetnode[key]:
1531 return retFailed
1532 if len(targetnode) > len(branch):
1533 return retFailed
1534 else:
1535 if type(branch) is list:
1536 if targetnode:
1537 return retFailed
1538
1539 elif type(targetnode) == dict:
1540 if key:
1541 if not targetnode.get(key,None):
1542 return retFailed
1543 else:
1544 if type(branch) is dict:
1545 if targetnode:
1546 return retFailed
1547
1548 elif m == MATCH_PRESENT:
1549 if type(targetnode) == list:
1550 if not type(key) is NoneType:
1551 if 0 <= key < len(targetnode):
1552 return retOK
1553 else:
1554 return retFailed
1555 else:
1556 return retFailed
1557
1558 elif type(targetnode) == dict:
1559 if key:
1560 if not targetnode.get(key,None):
1561 return retFailed
1562 return retOK
1563 else:
1564 return retFailed
1565
1566 elif m == MATCH_MEM:
1567 if type(targetnode) == list:
1568 if type(targetnode) != type(branch):
1569 raise JSONDataException("type","targetnode",str(type(targetnode)))
1570 for l in branch:
1571 try:
1572 if not targetnode.index(l):
1573 return retFailed
1574 except:
1575 return retFailed
1576 elif type(targetnode) == dict:
1577 if type(targetnode) == type(branch):
1578 raise JSONDataException("type","targetnode",str(type(targetnode)))
1579 for k,v in branch.items():
1580 if id(v) != id(targetnode.get(k)):
1581 return retFailed
1582 else:
1583 raise JSONDataException("type","targetnode",str(type(targetnode)))
1584 elif _matchcondition:
1585 raise JSONDataException("type","targetnode",str(type(targetnode)))
1586 return retOK
1587
1588 - def pop(self,key):
1589 """Transparently passes the 'pop()' call to 'self.data'."""
1590 return self.data.pop(key)
1591
1593 """Prints structured data.
1594
1595 Args:
1596 pretty: Activates pretty printer for treeview, else flat.
1597
1598 sourcefile: Loads data from 'sourcefile' into 'source'.
1599
1600 default:=None
1601 source: Prints data within 'source'.
1602
1603 default:=self.data
1604
1605 Returns:
1606 When successful returns 'True', else returns either 'False', or
1607 raises an exception.
1608
1609 Raises:
1610 JSONDataAmbiguity:
1611
1612 forwarded from 'json'
1613
1614 """
1615 source = kargs.get('source',None)
1616 sourcefile = kargs.get('sourcefile',None)
1617 if sourcefile and source:
1618 raise JSONDataAmbiguity('sourcefile/source',
1619 "sourcefile="+str(sourcefile),
1620 "source="+str(source)
1621 )
1622 if sourcefile:
1623 source = open(sourcefile)
1624 source = myjson.load(source)
1625 elif not source:
1626 source = self.data
1627
1628 if pretty:
1629 print myjson.dumps(source,indent=self.indent)
1630 else:
1631 print myjson.dumps(source)
1632
1634 """Prints structured schema.
1635
1636 Args:
1637 pretty: Activates pretty printer for treeview, else flat.
1638
1639 sourcefile: Loads schema from 'sourcefile' into 'source'.
1640
1641 default:=None
1642 source: Prints schema within 'source'.
1643
1644 default:=self.schema
1645
1646 Returns:
1647 When successful returns 'True', else returns either 'False', or
1648 raises an exception.
1649
1650 Raises:
1651 JSONDataAmbiguity:
1652
1653 forwarded from 'json'
1654
1655 """
1656 source = kargs.get('source',None)
1657 sourcefile = kargs.get('sourcefile',None)
1658 if sourcefile and source:
1659 raise JSONDataAmbiguity('sourcefile/source',
1660 "sourcefile="+str(sourcefile),
1661 "source="+str(source)
1662 )
1663 if sourcefile:
1664 source = open(sourcefile)
1665 source = myjson.load(source)
1666 elif not source:
1667 source = self.schema
1668
1669 if pretty:
1670 print myjson.dumps(source,indent=self.indent)
1671 else:
1672 print myjson.dumps(source)
1673
1674 - def setSchema(self,schemafile=None, targetnode=None, **kargs):
1675 """Sets schema or inserts a new branch into the current assigned schema.
1676
1677 The main schema(targetnode==None) is the schema related to the current
1678 instance. Additional branches could be added by importing the specific
1679 schema definitions into the main schema. These could either kept
1680 volatile as a temporary runtime extension, or stored into a new schema
1681 file in order as extension of the original for later combined reuse.
1682
1683 Args:
1684 schemafile:
1685 JSON-Schema filename for validation of the subtree/branch.
1686 See also **kargs['schema'].
1687 targetnode:
1688 Target container hook for the inclusion of the loaded branch.
1689 **kargs:
1690 schema:
1691 In-memory JSON-Schema as an alternative to schemafile.
1692 When provided the 'schemafile' is ignored.
1693
1694 default:=None
1695 validator: [default, draft3, off, ]
1696 Sets schema validator for the data file.
1697 The values are: default=validate, draft3=Draft3Validator,
1698 off=None.
1699
1700 default:= validate
1701 persistent:
1702 Stores the 'schema' persistently into 'schemafile' after
1703 completion of update including addition of branches.
1704 Requires valid 'schemafile'.
1705
1706 default:=False
1707
1708 Returns:
1709 When successful returns 'True', else returns either 'False', or
1710 raises an exception.
1711
1712 Raises:
1713
1714 JSONData:
1715
1716 JSONDataSourceFile:
1717
1718 JSONDataValue:
1719
1720 """
1721 if __debug__:
1722 if self.debug:
1723 print "DBG:setSchema:schemafile="+str(schemafile)
1724
1725
1726
1727
1728 datafile = None
1729 validator = self.validator
1730 persistent = False
1731 schema = None
1732 for k,v in kargs.items():
1733 if k == 'validator':
1734 if v == 'default' or v == MODE_SCHEMA_DRAFT4:
1735 validator = MODE_SCHEMA_DRAFT4
1736 elif v == 'draft3' or v == MODE_SCHEMA_DRAFT3:
1737 validator = MODE_SCHEMA_DRAFT3
1738 elif v == 'off' or v == MODE_SCHEMA_OFF:
1739 validator = MODE_SCHEMA_OFF
1740 else:
1741 raise JSONDataValue("unknown",k,str(v))
1742 elif k == 'schema':
1743 schema = v
1744 elif k == 'datafile':
1745 datafile = v
1746 elif k == 'persistent':
1747 persistent = v
1748
1749 if schemafile != None:
1750 self.schemafile = schemafile
1751 elif self.schemafile != None:
1752 schemafile = self.schemafile
1753 elif datafile != None:
1754 schemafile = os.path.splitext(self.datafile)[0]+'.jsd'
1755 if not os.path.isfile(schemafile):
1756 schemafile = None
1757 else:
1758 self.schemafile = schemafile
1759
1760 if not schemafile:
1761 if persistent:
1762 raise JSONDataTargetFile("open","JSONSchemaFilename",schemafile)
1763
1764
1765 if schema:
1766 pass
1767
1768 elif schemafile:
1769 schemafile = os.path.abspath(schemafile)
1770 self.schemafile = schemafile
1771 if not os.path.isfile(schemafile):
1772 raise JSONDataSourceFile("open","schemafile",str(schemafile))
1773 with open(schemafile) as schema_file:
1774 schema = myjson.load(schema_file)
1775 if schema == None:
1776 raise JSONDataSourceFile("read","schemafile",str(schemafile))
1777
1778 else:
1779 raise JSONDataSourceFile("open","schemafile",str(schemafile))
1780 pass
1781
1782
1783
1784
1785 if not targetnode:
1786 self.schema = schema
1787
1788 else:
1789
1790
1791 if type(targetnode) != type(schema):
1792 raise JSONDataException("type","target!=branch",str(type(targetnode))+"!="+str(type(schema)))
1793
1794 self.branch_add(targetnode,schema)
1795
1796 return schema != None
1797
1798 - def validate(self,data,schema,validator=None):
1799 """Validate data with schema by selected validator.
1800
1801 Args:
1802 data:
1803 JSON-Data.
1804 schema:
1805 JSON-Schema for validation.
1806 validator:
1807 Validator to be applied, current supported:
1808
1809 schema:
1810 In-memory JSON-Schema as an alternative to schemafile.
1811 When provided the 'schemafile' is ignored.
1812
1813 default:=None
1814 validator: [default, draft3, draft4, off, on, ]
1815 default|MODE_SCHEMA_ON
1816 The current default.
1817 draft3|MODE_SCHEMA_DRAFT3
1818 The first supported JSONSchema IETF-Draft.
1819 draft4|MODE_SCHEMA_DRAFT4
1820 The current supported JSONSchema IETF-Draft.
1821 off|MODE_SCHEMA_OFF:
1822 No validation.
1823
1824 Sets schema validator for the data file.
1825
1826 default:= MODE_SCHEMA_DRAFT4
1827
1828 Returns:
1829 When successful returns 'True', else returns either 'False', or
1830 raises an exception.
1831
1832 Raises:
1833 ValidationError:
1834 SchemaError:
1835 JSONDataValue:
1836
1837 """
1838 if not validator:
1839 validator = self.mode_schema
1840
1841 if validator == MODE_SCHEMA_DRAFT4:
1842 if self.verbose:
1843 print "VERB:Validate: draft4"
1844 try:
1845 jsonschema.validate(data, schema)
1846
1847
1848
1849 except ValidationError as e:
1850 print "ValidationError"
1851 print e
1852 print "#---"
1853 print dir(e)
1854 print "#---"
1855 print str(e)
1856 print "#---"
1857 print repr(e)
1858 print "#---"
1859 raise
1860 except SchemaError as e:
1861 print "SchemaError"
1862 print e
1863 print "#---"
1864 print dir(e)
1865 print "#---"
1866 print str(e)
1867 print "#---"
1868 print repr(e)
1869 print "#---"
1870 print "path:"+str(e.path)
1871 print "schema_path:"+str(e.schema_path)
1872 print "#---"
1873 raise
1874
1875 elif validator == MODE_SCHEMA_DRAFT3:
1876 if self.verbose:
1877 print "VERB:Validate: draft3"
1878 jsonschema.Draft3Validator(data, schema)
1879 elif validator != MODE_SCHEMA_OFF:
1880 raise JSONDataValue("unknown","validator",str(validator))
1881
1882 pass
1883
1884
1885 from jsondata.JSONPointer import JSONPointer
1886
1887