Package jsondata :: Module JSONData
[hide private]
[frames] | no frames]

Source Code for Module jsondata.JSONData

   1  # -*- coding:utf-8   -*- 
   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',): # pragma: no cover 
  33      raise Exception("Requires Python-2.6.* or higher") 
  34  # if version < '2.7': # pragma: no cover 
  35  #     raise Exception("Requires Python-2.7.* or higher") 
  36   
  37  #import termcolor 
  38  import copy 
  39  from types import NoneType 
  40   
  41  # 
  42  # Check whether the application has selected a verified JSON package 
  43  if sys.modules.get('json'): 
  44      import json as myjson #@UnusedImport 
  45  elif sys.modules.get('ujson'): 
  46      import ujson as myjson 
  47  else: 
  48      import json as myjson 
  49   
  50  # for now the only one supported 
  51  import jsonschema 
  52  from jsonschema import ValidationError,SchemaError 
  53  # Constants. 
  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  # match criteria for node comparison 
  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  # Sets display for inetractive JSON/JSONschema design. 
 108  _interactive = False 
 109   
 110  # generic exceptions for 'jsondata' 
 111  from JSONDataExceptions import JSONDataParameter,JSONDataException,JSONDataValue,JSONDataKeyError,JSONDataSourceFile,JSONDataTargetFile,JSONDataNodeType 
112 113 # 114 # special cases of exceptions 115 # 116 -class JSONDataAmbiguity(Exception):
117 """ Error ambiguity of provided parameters.""" 118
119 - def __init__(self,requested,*sources):
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
130 - def __str__(self):
131 return "JSONDataAmbiguity:"+self.s
132
133 -class JSONpl(list):
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
140 -class JSONData:
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 """
250 - def __init__(self,*args,**kargs):
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 # static final defaults 306 307 # prep import subcall 308 kimp={} 309 310 # JSON-Syntax modes 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 # default validator 322 323 if __debug__: 324 self.debug = False 325 self.verbose = False 326 327 # set display mode for errors 328 global _interactive 329 _interactive = kargs.get('interactive',False) 330 331 # The internal object schema for the framework - a fixed set of files as final MODE_SCHEMA_DRAFT4. 332 self.schema = kargs.get('schema',None) 333 334 # positional parameters dominate, remaining are MODE_SCHEMA_DRAFT4 335 if args: 336 for i in range(0,len(args)): 337 if i == 0: 338 self.data = args[i] 339 340 # 341 #*** Fetch parameters 342 # 343 for k,v in kargs.items(): 344 # if k == 'branch': 345 # self.branch = v 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': # controls validation by JSONschema 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 # Check data. 382 if type(self.data) is NoneType: 383 raise JSONDataParameter("value","data",str(self.data)) 384 385 # Validate. 386 if not self.schema and self.validator != MODE_SCHEMA_OFF: 387 raise JSONDataParameter("value","schema",str(self.schema)) 388 389 # INPUT-BRANCH: validate data 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
401 - def __add__(self,x):
402 """Adds the structure 'x' to 'self', performs deep-operation. 403 """ 404 return self
405
406 - def __and__(self):
407 """Gets the intersection of 'x' and 'self', performs deep-operation. 408 """ 409 return self
410
411 - def __call__(self, x):
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
427 - def __eq__(self, x):
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 : # all None is equal, 440 return True 441 return JSONData.getTreeDiff(self.data, x)
442
443 - def __iadd__(self,x):
444 """Adds the structure 'x' to 'self', performs deep-operation. 445 """ 446 return self
447
448 - def __iand__(self):
449 """Gets the intersection of 'x' and 'self', performs deep-operation. 450 """ 451 return self
452
453 - def __imod__(self,x):
454 """Returns the difference-modulo-set. 455 """ 456 return self
457
458 - def __imul__(self,x):
459 """Duplicates the elements of 'self' 'x' times. 460 """ 461 return self
462
463 - def __mul__(self,x):
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
481 - def __ior__(self,x):
482 """Returns the superset of branches and attributes. 483 """ 484 return self
485
486 - def __isub__(self,x):
487 """Returns the residue of X after each present element of 'x' is removed. 488 """ 489 return self
490
491 - def __ixor__(self,x):
492 """Returns the elements present in one only. 493 """ 494 return self
495
496 - def __mod__(self,x):
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
513 - def __radd__(self,x):
514 """Adds the structure 'x' to 'self', performs deep-operation. 515 """ 516 return self
517
518 - def __rand__(self):
519 """Gets the intersection of 'x' and 'self', performs deep-operation. 520 """ 521 return self
522
523 - def __rmod__(self,x):
524 """Returns the difference-modulo-set. 525 """ 526 return self
527
528 - def __rmul__(self,x):
529 """Duplicates the elements of 'self' 'x' times. 530 """ 531 return self
532
533 - def __or__(self,x):
534 """Returns the superset of branches and attributes. 535 """ 536 return self
537
538 - def __ror__(self,x):
539 """Returns the superset of branches and attributes. 540 """ 541 return self
542
543 - def __rsub__(self,x):
544 """Returns the residue of X after each present element of 'x' is removed. 545 """ 546 return self
547
548 - def __rxor__(self,x):
549 """Returns the elements present in one only. 550 """ 551 return self
552
553 - def __sub__(self,x):
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
571 - def __xor__(self):
572 """Returns the structure elements present in in one only. 573 """ 574 575 return self
576
577 - def __repr__(self):
578 """Dump data. 579 """ 580 # io = StringIO() 581 # myjson.dump(self.data, io) 582 # return io.getvalue() 583 return repr(self.data)
584 585
586 - def __str__(self):
587 """Dumps data by pretty print. 588 """ 589 return myjson.dumps(self.data, indent=self.indent, sort_keys=self.sort_keys)
590
591 - def __getitem__(self,key):
592 """Support of slices, for 'iterator' refer to self.__iter__. 593 """ 594 # self[key] 595 # self[i:j:k] 596 # x in self 597 # for x in self 598 if not self.data: 599 return None 600 return self.data[key]
601
602 - def __iter__(self):
603 """Provides an iterator for data. 604 """ 605 return iter(self.data)
606
607 - def __ne__(self, x):
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 # requires some more of a new path than for the node-only 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: # 0 is valid 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: # force replace of existing 762 return self.branch_add(targetnode, key, sourcenode) 763 elif self.isApplicable(targetnode, key, sourcenode, [MATCH_NEW]): # only new 764 return self.branch_add(targetnode, key, sourcenode) 765 else: # not applicable 766 return False
767
768 - def branch_create(self, targetnode, branch, value=None):
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 #FIXME: iterator 833 branch = branch.get_path_list() 834 835 if not type(branch) is list: 836 raise JSONDataException("value","branch",branch) 837 838 if targetnode == '': # RFC6901 - whole document 839 targetnode = self.data 840 841 if type(targetnode) == dict: 842 # Be aware, the special '-' could be a valid key, thus cannot be prohibited!!! 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): # see RFC6902 for '-'/append 857 raise JSONDataException("exists","branch",str(branch[0])) 858 elif unicode(branch[0]) == u'-': # see RFC6902 for '-'/append 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: # no source key provided 955 if type(key) is NoneType: # no keys provided at all, use source 956 raise JSONDataKeyError("missing","key",str(key)) 957 958 else: # use target key for both 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: # no source key provided 979 if type(key) is NoneType: # no keys provided at all, use source 980 raise JSONDataKeyError("missing","key",str(key)) 981 982 elif key == '-': # append all, due to missing 'skey' 983 if type(sourcenode) is list: # list to list 984 for v in reversed(sourcenode): 985 targetnode.append(v) 986 sourcenode.pop() 987 else: # is dict, requires 'skey' 988 raise JSONDataKeyError("type/dict","key",str(key)) 989 990 elif key < len(sourcenode): # use target key for both 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: # forcext is not applicable on explicit given keys 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
1035 - def branch_remove(self, targetnode, key):
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
1098 - def branch_replace(self,targetnode, key, sourcenode):
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
1135 - def branch_test(cls,targetnode, value):
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 : # all None is equal, 1153 return True 1154 return cls.getTreeDiff(targetnode, value) # value could be a branch itself
1155
1156 - def getData(self):
1157 """Returns the reference to data.""" 1158 return self.data
1159
1160 - def getSchema(self):
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 # assure JSON strings 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: # invalid types may have been eliminated already 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
1242 - def getPointerPath(cls,node,base,restype=FIRST):
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: # first layer - list of elements 1281 kl = 0 1282 if id(node) == id(base): # top node 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: # first layer - dict of elements 1302 if id(node) == id(base): # top node 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 #FIXME: for performance 1321 if res and restype == JSONData.FIRST: 1322 return [res[0]] 1323 return res
1324
1325 - def getCanonical(self,value):
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): # assumes a 'json' package type node 1351 return value 1352 elif type(value) in ( int, float, ): # assume a 'JSON' RFC7159 int, float 1353 return value 1354 elif type(value) in ( str, unicode, ): # assume a 'JSON' RFC7159 string 1355 return unicode(value) 1356 elif isinstance(value,JSONPointer): # assume the pointed value 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 #*** Fetch parameters 1435 # 1436 if not matchcondition: 1437 return True 1438 childattrlist = None 1439 _matchcondition = [] 1440 for v in matchcondition: 1441 #For now just passed through to self.isApplicable() 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': # provides a list of child attributes 1460 childattrlist = v 1461 #TODO: 1462 # elif k == 'schema': 1463 # sval = v 1464 1465 retOK = True # return in case of no-defined-condition or no-failing-condition 1466 1467 if isinstance(targetnode, JSONData): 1468 targetnode = targetnode.data 1469 if isinstance(branch, JSONData): 1470 branch = branch.data 1471 1472 # The first mandatory requirement definition if the type compatibility 1473 # of the plug and the plugin-element. 1474 if type(key) is NoneType and type(targetnode) != type(branch): 1475 raise JSONDataException("type","targetnode",str(type(targetnode))) 1476 1477 # set default 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: # handles multiple, does not need alist.branch_remove() 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
1592 - def printData(self, pretty=True, **kargs):
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 # yes, almost the same... 1627 1628 if pretty: 1629 print myjson.dumps(source,indent=self.indent) 1630 else: 1631 print myjson.dumps(source)
1632
1633 - def printSchema(self, pretty=True, **kargs):
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 # yes, almost the same... 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 #*** Fetch parameters 1727 # 1728 datafile = None 1729 validator = self.validator # use class settings as MODE_SCHEMA_DRAFT4 1730 persistent = False 1731 schema = None 1732 for k,v in kargs.items(): 1733 if k == 'validator': # controls validation by JSONschema 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: # change filename 1750 self.schemafile = schemafile 1751 elif self.schemafile != None: # use present 1752 schemafile = self.schemafile 1753 elif datafile != None: # derive coallocated from config 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: # persistence requires storage 1762 raise JSONDataTargetFile("open","JSONSchemaFilename",schemafile) 1763 1764 # schema for validation 1765 if schema: # use loaded 1766 pass 1767 1768 elif schemafile: # load from file 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: # missing at all 1779 raise JSONDataSourceFile("open","schemafile",str(schemafile)) 1780 pass 1781 1782 # 1783 # manage new branch data 1784 # 1785 if not targetnode: 1786 self.schema = schema 1787 1788 else: # data history present, so decide how to handle 1789 1790 # the container hook has to match for insertion- 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 #FIXME: 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 # avoid nested recursion problems 1887