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

Source Code for Module jsondata.JSONPointer

   1  # -*- coding:utf-8   -*- 
   2  """Provides classes for the JSONPointer definition in accordance to RFC6901. 
   3   
   4  The provided class JSONPointer internally stores and applies pointer data 
   5  as a list of keys and indexes with the additional cooperative caching of 
   6  the pointed in-memory node reference for fast access on data provided by 
   7  the packages 'json' and 'jsonschema'. Requests for the string representation 
   8  are transformed into a pointer path in accordance to RFC6901.  
   9   
  10  The JSONPointer class combines fast in-memory operations and pointer  
  11  arithmetics with standards compliant path strings at the API. 
  12   
  13  The JSONPointer class by itself is focused on the path pointer itself, though 
  14  the provided operations do not touch the content value. The pointer provides 
  15  the hook where the value has to be inserted.  
  16  """ 
  17  __author__ = 'Arno-Can Uestuensoez' 
  18  __maintainer__ = 'Arno-Can Uestuensoez' 
  19  __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints" 
  20  __copyright__ = "Copyright (C) 2015-2016 Arno-Can Uestuensoez @Ingenieurbuero Arno-Can Uestuensoez" 
  21  __version__ = '0.2.14' 
  22  __uuid__='63b597d6-4ada-4880-9f99-f5e0961351fb' 
  23   
  24  import sys 
  25   
  26  version = '{0}.{1}'.format(*sys.version_info[:2]) 
  27  if not version in ('2.6','2.7',): # pragma: no cover 
  28      raise Exception("Requires Python-2.6.* or higher") 
  29  # if version < '2.7': # pragma: no cover 
  30  #     raise Exception("Requires Python-2.7.* or higher") 
  31   
  32  from types import StringTypes,NoneType 
  33  import re 
  34  try: 
  35      from urllib import unquote 
  36      from itertools import izip 
  37  except ImportError: # Python 3 
  38      from urllib.parse import unquote 
  39      izip = zip 
  40   
  41   
  42  # Sets display for inetractive JSON/JSONschema design. 
  43  _interactive = False 
  44   
  45  # Notation a the API - in/out. 
  46  NOTATION_JSON = 0 # this is the default 
  47  """JSON notation in accordance to RFC7159 
  48  """ 
  49   
  50  NOTATION_HTTP_FRAGMENT = 1 
  51  """JSON notation in accordance to RFC7159 with RFC3986. 
  52  """ 
  53   
  54  VALID_NODE_TYPE = (dict, list, str, unicode, int, float, bool, NoneType,) 
  55  """Valid types of in-memory JSON node types.""" 
  56   
  57   
  58  CHARSET_UTF = 0 
  59  """Unicode.""" 
  60   
  61  CHARSET_STR = 1 
  62  """Python string.""" 
  63   
64 -class JSONPointerException(Exception):
65 pass
66
67 -class JSONPointer(list):
68 """Represents exactly one JSONPointer in compliance with IETF RFC6901. 69 This pointer could be processed by extension, reduction, and general modification 70 with support of provided methods and path arithmetic operators. 71 72 The JSONPointer is provided at the API as a utf(-8) string in accordance 73 to RFC6901, including RFC3869. 74 75 For enhancement of the processing performance by the underlying packages 76 'json' and 'jsonschema', the pointer is stored and applied in two variants. 77 78 * self.raw: Raw input of the pointer string for the logical API. 79 80 * self.ptr: Split elements of the pointer path within a list of keys, 81 for the programming interface. 82 83 The attribute 'self.ptr' contains the path elements in a 84 'list':: 85 86 ptrlist := (<EMPTY>|plist) 87 <EMPTY> := "empty list, represents the whole document" 88 plist := pkey [, plist ] 89 pkey := (''|int|keyname) 90 '' := "the empty string is a valid key too" 91 int := "integer index of an array item, just digits" 92 keyname := "the valid name of an object/property entry" 93 94 The JSONPointer:: 95 96 "/address/0/streetName" 97 98 is represented as:: 99 100 ['address', 0, 'streetName' ] 101 102 103 The methods and operators are handling the pointer itself, the values 104 referenced by the pointer are not modified. 105 106 The methods of this class support for multiple input format of 107 the JSONPointer. The parameter 'x' in the operations is defined as a 108 valid JSONPointer fragment. A pointer fragment is a part of a pointer, 109 which could be the complete pointer itself - the all-fragment. 110 111 The syntax element could be one of:: 112 113 'str': A string i accordance to RFC6901. Strings are represented 114 internally as unicode utf-8. 115 116 Here either the input parameter 'x' is split into 117 a list, or in case of combining operations, the self.ptr 118 attribute is 'joined' to be used for the method. 119 120 'int': A numeric value in case of an array index. This value 121 is internally handled for the string representation as a 122 unicode utf-8, whereas for the addressing of memory 123 objects the numeric integer value is stored and applied. 124 125 'JSONPointer': The attributes of the input object are used 126 with it's peers. 127 128 'list': Expects a path list containing: 129 - JSON object names 130 Names of the json objects. 131 - array indexes 132 Numeric index for arrays. 133 - JSONPointer 134 A JSONPointer object, the path is resolved as a section 135 of overall path. 136 137 The self.ptr attribute is applied for operations. 138 139 The node reference is cached by the 'get_node' and 'get_node_or_value' 140 method, thus could be accessed by 'self.node', but is not monitored 141 to be valid. Another call of the method reloads the cache by evaluating 142 the pointer value on the document again. 143 144 The provided value is internally stored as a raw input value, and a list 145 of keys and indexes for access to in-memory data as provided by the 146 packages 'json' and 'jsonschema'. Requests for the string representation 147 are transformed into a pointer path in accordance to RFC6901. This provides 148 for fast access in case of pointer arithmetics, while providing standards 149 conform path strings at the interface. 150 """ 151 VALID_INDEX = re.compile('0|[1-9][0-9]*$') 152 """Regular expression for valid numerical index.""" 153
154 - def __init__(self,ptr,replace=True,**kargs):
155 """ Converts and stores a JSONPointer as a list. 156 157 Processes the ABNF of a JSON Pointer from RFC6901. 158 159 Args: 160 ptr: A JSONPointer to be represented by this object. The 161 supported formats are: 162 'str': A string i accordance to RFC6901 163 JSONPointer: A valid object, will be copied 164 into this, see 'deep'. 165 'list': expects a path list, where each item 166 is processed for escape and unquote. 167 replace: Replaces masked characters. 168 **kargs: 169 deep: Applies for copy operations on structured data 170 'deep' when 'True', else 'swallow' only, which is 171 just a link to the data structure. Flat data types 172 are copied by value in any case. 173 node: Force to set the pointed node in the internal cache. 174 debug: Enable debugging. 175 176 Returns: 177 When successful returns 'True', else returns either 'False', or 178 raises an exception. 179 Success is the complete addition only, thus one failure returns 180 False. 181 182 Raises: 183 JSONPointerException: 184 185 """ 186 self.debug = kargs.get('debug',False) 187 self.node = kargs.get('node',None) # cache for reuse 188 self.deep = deep = kargs.get('deep',False) 189 if ptr and type(ptr) in (str,unicode) and ptr[0] is '#': # pointer are unicode only 190 ptr = ptr[1:] 191 192 if type(ptr) in (int,float): # pointer are unicode only 193 ptr=u'/'+unicode(ptr) 194 195 if ptr == '': # shortcut for whole document, see RFC6901 196 self.raw = '' 197 self = [] 198 return None 199 200 elif ptr == '/': # shortcut for empty tag at top-level, see RFC6901 201 self.raw = ptr 202 self.append('') 203 return None 204 205 elif isinstance(ptr, StringTypes): # string in accordance to RFC6901 206 if ptr[0] == '/': 207 self.extend(ptr[1:].split('/')) 208 else: 209 self.extend(ptr.split('/')) 210 ptr = u'/'+ptr # any pointer is absolute due to RFC6901, force it silently for smart loops 211 212 if deep: 213 self.raw = ptr[:] 214 else: 215 self.raw = ptr 216 217 elif isinstance(ptr, JSONPointer): # copy a pointer, source has to be valid 218 if deep: 219 self.raw = ptr.raw[:] 220 self.extend(ptr.copy_path()) 221 else: 222 self.raw = ptr.raw 223 self.extend(ptr) 224 225 elif type(ptr) is list: # list of entries in accordance to RFC6901, and JSONPointer 226 def presolv(p0): 227 if isinstance(p0, JSONPointer): # copy constructor 228 return p0.ptr 229 elif p0 in ('', '/'): 230 return p0 231 elif type(p0) in (str,unicode): 232 return p0 233 elif type(p0) in (int,float): 234 return str(p0) 235 else: 236 raise JSONPointerException("Invalid nodepart:"+str(p0)) 237 return p0
238 if deep: 239 self.extend(map(lambda s:s[:],ptr)) 240 else: 241 self.extend(map(presolv,ptr)) 242 self.raw = '/'+'/'.join(self) 243 else: 244 if not ptr: 245 self.raw = None 246 return None 247 raise JSONPointerException("Pointer type not supported:",type(ptr)) 248 249 if replace: 250 x = map(lambda p: type(p) in (str,unicode) and unquote(p).replace('~1', '/').replace('~0', '~') or p,self) # 6901-escaped, generic chars-quote 251 del self[:] 252 self.extend(x) 253 #FIXME: check wheter the assumption is viable 254 # SPECIAL: assumes digit only as array index 255 def checkint(ix): 256 if type(ix) in (str,unicode,) and ix.isdigit(): 257 return int(ix) 258 return ix
259 x = map(checkint,self) 260 del self[:] 261 self.extend(x) 262
263 - def __add__(self, x):
264 """Appends a Pointer to self. 265 266 Args: 267 x: A valid JSONPointer fragment. 268 269 Returns: 270 A new object of JSONPointer 271 272 Raises: 273 JSONPointerException: 274 275 """ 276 ret = JSONPointer(self) 277 if type(x) in (str,unicode) and x[0] is '#': # pointer are unicode only, RFC6901/RFC3829 278 x = x[1:] 279 280 if x == '': # whole document, RFC6901 281 raise JSONPointerException("Cannot add the whole document") 282 elif x == u'/': # empty tag 283 ret.raw += x 284 ret.append('') 285 286 elif isinstance(x, JSONPointer): 287 ret.raw += x.raw 288 ret.extend(x) 289 elif type(x) == list: 290 ret.raw += u'/'+u'/'.join(x) 291 ret.extend(x) 292 elif type(x) in (str,unicode): 293 if x[0] == u'/': 294 ret.extend(x[1:].split('/')) 295 ret.raw += x 296 else: 297 ret.extend(x.split('/')) 298 ret.raw += u'/'+x 299 elif type(x) is int: 300 ret.append(x) 301 ret.raw += u'/'+unicode(x) 302 elif type(x) is NoneType: 303 return ret 304 305 else: 306 raise JSONPointerException() 307 return ret
308
309 - def __call__(self, x):
310 """Evaluates the pointed value from the document. 311 312 Args: 313 x: A valid JSON document. 314 315 Returns: 316 The pointed value, or None. 317 318 Raises: 319 JSONPointerException 320 """ 321 return self.get_node_or_value(x)
322
323 - def __eq__(self, x):
324 """Compares this pointer with x. 325 326 Args: 327 x: A valid Pointer. 328 329 Returns: 330 True or False 331 332 Raises: 333 JSONPointerException 334 """ 335 s = u'/'+u'/'.join(map(unicode,self)) 336 337 if isinstance(x, JSONPointer): 338 return s == x.get_pointer() 339 elif type(x) == list: 340 #return s == unicode("/"+'/'.join(x)) 341 return s == JSONPointer(x).get_pointer() 342 elif type(x) in (str,unicode): 343 return s == unicode(x) 344 elif type(x) in (int): 345 return s == u'/'+unicode(x) 346 elif type(x) is NoneType: 347 return False 348 349 else: 350 raise JSONPointerException() 351 return False
352
353 - def __ge__(self, x):
354 """Checks containment(>=) of another pointer within this. 355 356 The weight of contained entries is the criteria, though 357 the shorter is the bigger. This is true only in case of 358 a containment relation. 359 360 The number of equal path pointer items is compared. 361 362 Args: 363 x: A valid Pointer. 364 365 Returns: 366 True or False 367 368 Raises: 369 JSONPointerException: 370 371 """ 372 s = u'/'+u'/'.join(map(unicode,self)) 373 374 if isinstance(x, JSONPointer): 375 px = x.get_pointer() 376 elif type(x) == list: 377 px = u"/"+'/'.join(map(unicode,x)) 378 elif type(x) in (str,unicode): 379 px = unicode(x) 380 elif type(x) in (int): 381 px = u'/'+unicode(x) 382 elif type(x) is NoneType: 383 return True 384 385 else: 386 raise JSONPointerException() 387 388 if s > px: # the shorter is the bigger 389 return False 390 if unicode(px).startswith(unicode(s)): # matching part has to be literal 391 return True 392 else: 393 return False
394
395 - def __gt__(self, x):
396 """Checks containment(>) of another pointer or object within this. 397 398 The number of equal items is compared. 399 400 Args: 401 x: A valid Pointer. 402 403 Returns: 404 True or False 405 406 Raises: 407 JSONPointerException: 408 """ 409 s = u'/'+u'/'.join(map(unicode,self)) 410 411 if isinstance(x, JSONPointer): 412 px = x.get_pointer() 413 elif type(x) == list: 414 px = u"/"+u'/'.join(map(unicode,x)) 415 elif type(x) in (str,unicode): 416 px = unicode(x) 417 elif type(x) in (int): 418 px = u'/'+unicode(x) 419 elif type(x) is NoneType: 420 return True 421 422 else: 423 raise JSONPointerException() 424 425 if s >= px: # the shorter is the bigger, so false in any case 426 return False 427 if unicode(px).startswith(unicode(s)): # matching part has to be literal 428 return True 429 else: 430 return False
431
432 - def __iadd__(self, x):
433 """Add in place x to self, appends a path. 434 435 Args: 436 x: A valid Pointer. 437 438 Returns: 439 'self' with updated pointer attributes 440 441 Raises: 442 JSONPointerException: 443 """ 444 if type(x) == list: 445 self.raw += unicode('/'+'/'.join(x)) 446 self.extend(x) 447 elif isinstance(x, JSONPointer): 448 if x.raw[0] != u'/': 449 self.raw += u'/'+x.raw 450 else: 451 self.raw = x.raw 452 self.extend(x) 453 elif type(x) is int: 454 self.append(unicode(x)) 455 self.raw += u'/'+unicode(x) 456 elif x == '': # whole document, RFC6901 457 raise JSONPointerException("Cannot add the whole document") 458 elif x == u'/': # empty tag 459 self.raw += x 460 self.append('') 461 elif type(x) in (str,unicode): 462 if x[0] == u'/': 463 self.extend(x[1:].split('/')) 464 self.raw += x 465 else: 466 self.extend(x.split('/')) 467 self.raw += u'/'+x 468 elif type(x) is NoneType: 469 return self 470 471 else: 472 raise JSONPointerException() 473 return self
474
475 - def __le__(self, x):
476 """Checks containment(<=) of this pointer within another. 477 478 The number of equal items is compared. 479 480 Args: 481 x: A valid Pointer. 482 483 Returns: 484 True or False 485 486 Raises: 487 JSONPointerException: 488 """ 489 s = u'/'+u'/'.join(map(unicode,self)) 490 491 if isinstance(x, JSONPointer): 492 px = x.get_pointer() 493 elif type(x) == list: 494 px = u"/"+u'/'.join(map(unicode,x)) 495 elif type(x) in (str,unicode): 496 px = unicode(x) 497 elif type(x) in (int): 498 px = u'/'+unicode(x) 499 elif type(x) is NoneType: 500 return False 501 502 else: 503 raise JSONPointerException() 504 505 if s < px: # the shorter is the bigger 506 return False 507 if unicode(s).startswith(unicode(px)): # matching part has to be literal 508 return True
509
510 - def __lt__(self, x):
511 """Checks containment(<) of this pointer within another. 512 513 The number of equal items is compared. 514 515 Args: 516 x: A valid Pointer. 517 518 Returns: 519 True or False 520 521 Raises: 522 JSONPointerException: 523 """ 524 s = u'/'+u'/'.join(map(unicode,self)) 525 526 if isinstance(x, JSONPointer): 527 px = x.get_pointer() 528 elif type(x) == list: 529 px = u"/"+u'/'.join(map(unicode,x)) 530 elif type(x) in (str,unicode): 531 px = unicode(x) 532 elif type(x) in (int): 533 px = u'/'+unicode(x) 534 elif type(x) is NoneType: 535 return False 536 537 else: 538 raise JSONPointerException() 539 540 if s <= px: # the shorter is the bigger 541 return False 542 if unicode(s).startswith(unicode(px)): # matching part has to be literal 543 return True
544
545 - def __ne__(self, x):
546 """Compares this pointer with x. 547 548 Args: 549 x: A valid Pointer. 550 551 Returns: 552 True or False 553 554 Raises: 555 JSONPointerException 556 """ 557 return not self.__eq__(x)
558
559 - def __radd__(self, x):
560 """Adds itself as the right-side-argument to the left. 561 562 This method appends 'self' to a path fragment on the left. 563 Therefore it adds the path separator on it's left side only. 564 The left side path fragment has to maintain to be in 565 accordance to RFC6901 by itself. 566 567 Once 'self' is added to the left side, it terminates it's 568 life cycle. Thus another simultaneous add operation is 569 handled by the resulting other element. 570 571 Args: 572 x: A valid Pointer. 573 574 Returns: 575 The updated input of type 'x' as 'x+S(x)' 576 577 Raises: 578 JSONPointerException: 579 """ 580 if x == '': # whole document, RFC6901 581 return u'/'+u'/'.join(map(unicode,self)) 582 elif x == u'/': # empty tag 583 return x+u'/'+u'/'.join(map(unicode,self)) 584 elif type(x) is int: 585 return u'/'+unicode(x)+u'/'+u'/'.join(map(unicode,self)) 586 elif type(x) in (str,unicode): 587 return x+u'/'+u'/'.join(map(unicode,self)) 588 elif type(x) == list: 589 return x.extend(self) 590 else: 591 raise JSONPointerException() 592 return x
593
594 - def __repr__(self):
595 """Returns the attribute self.raw, which is the raw input JSONPointer. 596 """ 597 return unicode(super(JSONPointer,self).__repr__())
598
599 - def __str__(self):
600 """Returns the string for the processed path. 601 """ 602 ret = self.get_pointer() 603 if ret == '': 604 return "''" 605 return ret
606 607 # 608 # --- 609 # 610
611 - def check_node_or_value(self,jsondata,parent=False):
612 """Checks the existance of the corresponding node within the JSON document. 613 614 Args: 615 jsondata: A valid JSON data node. 616 parent: Return the parent node of the pointed value. 617 618 Returns: 619 True or False 620 621 Raises: 622 JSONPointerException: 623 forwarded from json 624 """ 625 if self == []: # special RFC6901, whole document 626 return jsondata 627 if self == ['']: # special RFC6901, '/' empty top-tag 628 return jsondata[''] 629 630 if type(jsondata) not in VALID_NODE_TYPE: 631 # concrete info for debugging for type mismatch 632 raise JSONPointerException("Invalid nodetype parameter:"+str(type(jsondata))) 633 634 if parent: 635 for x in self.ptr[:-1]: 636 jsondata=jsondata.get(x,False) 637 if not jsondata: 638 return False 639 else: 640 for x in self.ptr: 641 jsondata=jsondata.get(x,False) 642 if not jsondata: 643 return False 644 645 if type(jsondata) not in VALID_NODE_TYPE: 646 # concrete info for debugging for type mismatch 647 raise JSONPointerException("Invalid path nodetype:"+str(type(jsondata))) 648 self.node = jsondata # cache for reuse 649 return True
650
651 - def copy_path_list(self,parent=False):
652 """Returns a deep copy of the objects pointer path list. 653 654 Args: 655 parent: The parent node of the pointer path. 656 657 Returns: 658 A copy of the path list. 659 660 Raises: 661 none 662 """ 663 if self == []: # special RFC6901, whole document 664 return [] 665 if self == ['']: # special RFC6901, '/' empty top-tag 666 return [''] 667 668 if parent: 669 return map(lambda s:s[:],self[:-1]) 670 else: 671 return map(lambda s:s[:],self[:])
672
673 - def get_node(self,jsondata,parent=False):
674 """Gets the corresponding node reference for a JSON container type. 675 676 This method gets nodes of container types. Container 677 types of JSON are 'array' a.k.a. in Python 'list', 678 and 'objects' a.k.a. in Python 'dict'. 679 680 Due to the special case of RFC6902 'append' by the array 681 index '-' in combination of the add rules a special 682 exception-treatment is required, for details refer to RFC6902. 683 684 The 'get_node' method therefore returns only an existing 685 node of a of valid non-ambiguous path pointer. This 686 excludes pointers containing the symbolic index '-' for 687 an array component. 688 689 See also related methods: 690 691 get_node_and_child: For Python access to a child node 692 within a container by the container itself, and the 693 item key. 694 get_node_exist: For the application of partial valid 695 pointer paths of new branches. 696 get_node_or_value: For any type of pointed item, either 697 a node, or a value. 698 699 Args: 700 jsondata: A valid JSON data node. 701 parent: Return the parent node of the pointed value. 702 When parent is selected, the pointed child node 703 is not verified. 704 705 Returns: 706 The node reference. 707 708 Raises: 709 JSONPointerException: 710 forwarded from json 711 """ 712 if self == []: # special RFC6901, whole document 713 return jsondata 714 if len(self) == 1 and self[0] == '': # special RFC6901, '/' empty top-tag 715 return jsondata[0] 716 717 if type(jsondata) not in (dict, list): 718 # concrete info for debugging for type mismatch 719 raise JSONPointerException("Invalid nodetype parameter:"+str(type(jsondata))) 720 721 try: 722 if parent: 723 for x in self[:-1]: 724 jsondata=jsondata[x] # want the exception, the keys within the process has to match 725 else: 726 for x in self: 727 jsondata=jsondata[x] # want the exception, the keys within the process has to match 728 except Exception as e: 729 raise JSONPointerException("Requires existing Node("+str(self.index(x))+"):"+str(x)+" of "+str(self)+":"+str(e)) 730 if type(jsondata) not in (dict, list): 731 # concrete info for debugging for type mismatch 732 raise JSONPointerException("Invalid path nodetype:"+str(type(jsondata))) 733 self.node = jsondata # cache for reuse 734 return jsondata
735
736 - def get_node_and_child(self,jsondata):
737 """Returns a tuple containing the parent node and the child. 738 739 Args: 740 jsondata: A valid JSON data node. 741 742 Returns: 743 The the tuple: 744 (n,c): n: Node reference to parent container. 745 c: Key for the child entry, either an 746 index 'int', or a key ('str', 'unicode'). 747 Raises: 748 JSONPointerException: 749 forwarded from json 750 """ 751 n = self.get_node(jsondata,True) 752 return n,self[-1]
753
754 - def get_node_or_value(self,jsondata,valtype=None,parent=False):
755 """Gets the corresponding node reference or the JSON value of a leaf. 756 757 Relies on the standard package 'json' by 'Bob Ippolito <bob@redivi.com>'. 758 This package supports in the current version the following types: 759 760 +---------------+-------------------+ 761 | JSON | Python | 762 +===============+===================+ 763 | object | dict | 764 +---------------+-------------------+ 765 | array | list | 766 +---------------+-------------------+ 767 | string | unicode | 768 +---------------+-------------------+ 769 | number (int) | int, long | 770 +---------------+-------------------+ 771 | number (real) | float | 772 +---------------+-------------------+ 773 | true | True | 774 +---------------+-------------------+ 775 | false | False | 776 +---------------+-------------------+ 777 | null | None | 778 +---------------+-------------------+ 779 780 It also understands ``NaN``, ``Infinity``, and 781 ``-Infinity`` as their corresponding ``float`` 782 values, which is outside the JSON spec. 783 784 785 The supported standard value types for Python 786 of get_node_or_value() are mapped automatically 787 as depicted in the following table. Additional 788 bindings may be implemented by sub-classing. 789 790 +------------------------+-------------------+ 791 | JSONPointer(jsondata) | Python-valtype | 792 +========================+===================+ 793 | object (dict) | dict | 794 +------------------------+-------------------+ 795 | array (list) | list | 796 +------------------------+-------------------+ 797 | array (tuple) | list | 798 +------------------------+-------------------+ 799 | string | unicode | 800 +------------------------+-------------------+ 801 | number (int) | int | 802 +------------------------+-------------------+ 803 | number (long) | long | 804 +------------------------+-------------------+ 805 | number (float) | float | 806 +------------------------+-------------------+ 807 | *number (double) | float | 808 +------------------------+-------------------+ 809 | number (octal) | int | 810 +------------------------+-------------------+ 811 | number (hex) | int | 812 +------------------------+-------------------+ 813 | number (binary) | int | 814 +------------------------+-------------------+ 815 | number (complex) | - (custom) | 816 +------------------------+-------------------+ 817 | true | True | 818 +------------------------+-------------------+ 819 | false | False | 820 +------------------------+-------------------+ 821 | null | None | 822 +------------------------+-------------------+ 823 824 The mappings in detail are: 825 826 * object(dict) => dict: 827 {a:b} - native Python dictionary 828 829 * array(list) => list: 830 [a,b] - native Python list 831 832 * (*)array(tuple) => list: 833 (a,b) - native Python list 834 835 * string(str) => unicode" 836 "abc" - native Python unicode string UTF-8 837 838 * number(int) => int: 839 1234, −24, 0 - Integers (unlimited precision) 840 841 * number(long) => int: 842 1234, −24, 0 - Integers (unlimited precision) 843 844 * number(float) => float: 845 1.23, 3.14e-10, 4E210, 4.0e+210, 1., .1 - 846 Floating-point (normally implemented as C doubles in CPython) 847 848 * (*)number(double) => float: 849 1.23, 3.14e-10, 4E210, 4.0e+210, 1., .1 - 850 Floating-point (normally implemented as C doubles in CPython) 851 852 * number(octal) => int: 853 0o177 - 854 Octal, hex, and binary literals for integers2 855 856 * number(hex) => int: 857 0x9ff - Octal, hex, and binary literals for integers2 858 859 * number(binary) => int: 860 0b1111 - Octal, hex, and binary literals for integers2 861 862 * number(complex) => <not-supported>(requires custom): 863 3+4j, 3.0+4.0j, 3J - Complex numbers 864 865 * true(True) => boolean(True): 866 True - native Python boolean 867 868 * false(False) => boolean(False): 869 False - native Python boolean 870 871 * null(None) => NoneType(None): 872 False - native Python NoneType 873 874 Args: 875 jsondata: A valid JSON data node. 876 valtype: Type of requested value. 877 parent: Return the parent node of the pointed value. 878 879 Returns: 880 The node reference. 881 882 Raises: 883 JSONPointerException: 884 forwarded from json 885 """ 886 if not self: # == [] : special RFC6901, whole document 887 return jsondata 888 if len(self) == 1 and self[0] == '': # special RFC6901, '/' empty top-tag 889 return jsondata[''] 890 891 if type(jsondata) not in VALID_NODE_TYPE: # requires an object or an array as input 892 # concrete info for debugging for type mismatch 893 raise JSONPointerException("Invalid nodetype parameter:"+str(type(jsondata))) 894 895 try: 896 if parent: # request for container 897 for x in self[:-1]: 898 jsondata=jsondata[x] # want the exception 899 else: 900 for x in self: 901 jsondata=jsondata[x] # want the exception 902 except Exception as e: 903 raise JSONPointerException("Node("+str(self.index(x))+"):"+str(x)+" of "+str(self)+":"+str(e)) 904 if valtype: # requested value type 905 # fix type ambiguity for numeric 906 if valtype in (int,float): 907 if jsondata.isdigit(): 908 jsondata = int(jsondata) 909 elif valtype in (int,float): 910 #FIXME: 911 if jsondata.isdigit(): 912 jsondata = float(jsondata) 913 914 if not type(jsondata) is valtype: 915 raise JSONPointerException("Invalid path value type:"+str(type(valtype))+" != "+str(type(jsondata))) 916 else: # in general valid value types - RFC4729,RFC7951 917 if type(jsondata) not in VALID_NODE_TYPE: 918 raise JSONPointerException("Invalid path nodetype:"+str(type(jsondata))) 919 self.node = jsondata # cache for reuse 920 return jsondata
921
922 - def get_node_exist(self,jsondata,parent=False):
923 """Returns the node for valid part of the pointer, and the remaining part. 924 925 This method works similar to the 'get_node' method, whereas it 926 handles partial valid path pointers, which may also include 927 a '-' in accordance to RFC6902. 928 929 Therefore the non-ambiguous part of the pointer is resolved, 930 and returned with the remaining part for a newly create. 931 Thus this method is in particular foreseen to support the 932 creation of new sub data structures. 933 934 The 'get_node' method therefore returns a list of two elements, 935 the first is the node reference, the second the list of the 936 remaining path pointer components. The latter may be empty in 937 case of a fully valid pointer. 938 939 940 Args: 941 jsondata: A valid JSON data node. 942 parent: Return the parent node of the pointed value. 943 944 Returns: 945 The node reference, and the remaining part. 946 ret:=[ node, [<remaining-path-components-list>] ] 947 948 Raises: 949 JSONPointerException: 950 forwarded from json 951 """ 952 if self == []: # special RFC6901, whole document 953 return jsondata 954 if self == ['']: # special RFC6901, '/' empty top-tag 955 return jsondata[''] 956 957 if type(jsondata) not in (dict, list): 958 # concrete info for debugging for type mismatch 959 raise JSONPointerException("Invalid nodetype parameter:"+str(type(jsondata))) 960 remaining = None 961 try: 962 if parent: 963 for x in self[:-1]: 964 remaining = x 965 jsondata=jsondata[x] # want the exception, the keys within the process has to match 966 else: 967 for x in self: 968 remaining = x 969 jsondata=jsondata[x] # want the exception, the keys within the process has to match 970 except Exception: 971 if parent: 972 remaining = self[self.index(remaining):-1] 973 else: 974 remaining = self[self.index(remaining):] 975 if type(jsondata) not in (dict, list): 976 # concrete info for debugging for type mismatch 977 raise JSONPointerException("Invalid path nodetype:"+str(type(jsondata))) 978 self.node = jsondata # cache for reuse 979 return [jsondata, remaining]
980
981 - def get_path_list(self):
982 """Gets for the corresponding path list of the object pointer for in-memory access on the data of the 'json' package. 983 984 Args: 985 none 986 987 Returns: 988 The path list. 989 990 Raises: 991 none 992 """ 993 if __debug__: 994 if self.debug: 995 print repr(self) 996 return list(self)
997
998 - def get_path_list_and_key(self):
999 """Gets for the corresponding path list of the object pointer for in-memory access on the data of the 'json' package. 1000 1001 Args: 1002 none 1003 1004 Returns: 1005 The path list. 1006 1007 Raises: 1008 none 1009 """ 1010 if len(self)>2: 1011 return self[:-1],self[-1] 1012 elif len(self)==1: 1013 return [],self[-1] 1014 elif len(self)==0: 1015 return [],None
1016
1017 - def get_pointer(self,forcenotation=None,parent=False):
1018 """Gets the objects pointer in compliance to RFC6901. 1019 1020 Args: 1021 forcenotation: Force the output notation to: 1022 None := NOTATION_JSON, 1023 NOTATION_JSON = 0, 1024 NOTATION_HTTP_FRAGMENT = 1 1025 1026 parent: Get parent of selected node. 1027 1028 Returns: 1029 The pointer in accordance to RFC6901. 1030 1031 Raises: 1032 none 1033 """ 1034 if not self: # ==[] : special RFC6901, whole document 1035 return '' 1036 if len(self) == 1 and self[0] == '': # special RFC6901, '/' empty top-tag 1037 return '/' 1038 1039 if parent: 1040 return unicode('/'+'/'.join(map(unicode,self[:-1]))) 1041 else: 1042 return unicode('/'+'/'.join(map(unicode,self)))
1043
1044 - def get_raw(self):
1045 """Gets the objects raw 6901-pointer. 1046 1047 Args: 1048 none 1049 1050 Returns: 1051 The raw path. 1052 1053 Raises: 1054 none 1055 """ 1056 return self.raw
1057
1058 - def iter_path(self,jsondata=None,parent=False,rev=False):
1059 """Iterator for the elements of the path pointer itself. 1060 1061 Args: 1062 jsondata: If provided a valid JSON data node, the 1063 path components are successively verified on 1064 the provided document. If None the path pointer 1065 components are just iterated. 1066 parent: Uses the path pointer to parent node. 1067 rev: Reverse the order, start with last. 1068 1069 Returns: 1070 Yields the iterator for the current path pointer 1071 component. 1072 1073 Raises: 1074 JSONPointerException: 1075 forwarded from json 1076 """ 1077 if self.ptr == []: # special RFC6901, whole document 1078 yield '' 1079 elif self.ptr == ['']: # special RFC6901, '/' empty top-tag 1080 yield '/' 1081 else: 1082 if jsondata and type(jsondata) not in (dict, list): 1083 # concrete info for debugging for type mismatch 1084 raise JSONPointerException("Invalid nodetype parameter:"+str(type(jsondata))) 1085 1086 if rev: # reverse 1087 if parent: # for parent 1088 ptrpath = self.ptr[:-1:-1] 1089 else: # full path 1090 ptrpath = self.ptr[::-1] 1091 else: 1092 if parent: # for parent 1093 ptrpath = self.ptr[:-1] 1094 else: # full path 1095 ptrpath = self.ptr 1096 1097 try: 1098 x = ptrpath[0] 1099 for x in ptrpath: 1100 if jsondata: 1101 jsondata=jsondata[x] # want the exception, the keys within the process has to match 1102 if type(jsondata) not in (dict, list): 1103 # concrete info for debugging for type mismatch 1104 raise JSONPointerException("Invalid path nodetype:"+str(type(jsondata))) 1105 yield x 1106 except Exception as e: 1107 raise JSONPointerException("Node("+str(ptrpath.index(x))+"):"+str(x)+" of "+str(self.ptr)+":"+str(e)) 1108 self.node = jsondata # cache for reuse
1109
1110 - def iter_path_nodes(self,jsondata,parent=False,rev=False):
1111 """Iterator for the elements the path pointer points to. 1112 1113 Args: 1114 jsondata: A valid JSON data node. 1115 parent: Uses the path pointer to parent node. 1116 rev: Reverse the order, start with last. 1117 1118 Returns: 1119 Yields the iterator of the current node reference. 1120 1121 Raises: 1122 JSONPointerException: 1123 forwarded from json 1124 """ 1125 if self.ptr == []: # special RFC6901, whole document 1126 yield jsondata 1127 elif self.ptr == ['']: # special RFC6901, '/' empty top-tag 1128 yield jsondata[''] 1129 else: 1130 if type(jsondata) not in (dict, list): 1131 # concrete info for debugging for type mismatch 1132 raise JSONPointerException("Invalid nodetype parameter:"+str(type(jsondata))) 1133 1134 if rev: # reverse 1135 if parent: # for parent 1136 ptrpath = self.ptr[:-1:-1] 1137 else: # full path 1138 ptrpath = self.ptr[::-1] 1139 else: 1140 if parent: # for parent 1141 ptrpath = self.ptr[:-1] 1142 else: # full path 1143 ptrpath = self.ptr 1144 1145 try: 1146 x = ptrpath[0] 1147 for x in ptrpath: 1148 jsondata=jsondata[x] # want the exception, the keys within the process has to match 1149 if type(jsondata) not in (dict, list): 1150 # concrete info for debugging for type mismatch 1151 raise JSONPointerException("Invalid path nodetype:"+str(type(jsondata))) 1152 yield jsondata 1153 except Exception as e: 1154 raise JSONPointerException("Node("+str(ptrpath.index(x))+"):"+str(x)+" of "+str(self.ptr)+":"+str(e)) 1155 self.node = jsondata # cache for reuse
1156