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