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 
   3  accordance to RFC6901 and relative pointers draft-1/2018. 
   4  """ 
   5  from __future__ import absolute_import 
   6  from __future__ import print_function 
   7   
   8  import re 
   9  import copy 
  10   
  11  from jsondata import V3K, ISSTR, JSONPointerError, JSONPointerTypeError, \ 
  12      C_SHALLOW, C_DEEP, \ 
  13      M_FIRST, M_LAST, \ 
  14      rtypes2num, RT_LST, RT_JSONPOINTER, \ 
  15      verify2num, V_FINAL, V_STEPS 
  16   
  17   
  18  __author__ = 'Arno-Can Uestuensoez' 
  19  __maintainer__ = 'Arno-Can Uestuensoez' 
  20  __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints" 
  21  __copyright__ = "Copyright (C) 2015-2016 Arno-Can Uestuensoez" \ 
  22                  " @Ingenieurbuero Arno-Can Uestuensoez" 
  23  __version__ = '0.2.21' 
  24  __uuid__ = '63b597d6-4ada-4880-9f99-f5e0961351fb' 
  25   
  26  if V3K: 
  27      unicode = str 
  28      from urllib.parse import unquote as url_unquote  # @UnresolvedImport @UnusedImport pylint: disable=import-error 
  29  else: 
  30      from urllib import unquote as url_unquote  # @UnresolvedImport  @Reimport pylint: disable=import-error 
  31   
  32  # Sets display for inetractive JSON/JSONschema design. 
  33  _interactive = False 
  34   
  35  from jsondata import NOTATION_JSON, NOTATION_JSON_REL, NOTATION_HTTP_FRAGMENT 
  36   
  37  VALID_NODE_TYPE = ( 
  38      dict, 
  39      list, 
  40      str, 
  41      unicode, 
  42      int, 
  43      float, 
  44      bool, 
  45      None, 
  46      )  #: Valid types of in-memory JSON node types. 
  47   
  48  CHARSET_UTF = 0  #: Unicode. 
  49  CHARSET_STR = 1  #: Python string. 
  50   
  51  # 
  52  # relative JSON Pointer - [RELPOINTER] draft-handrews-relative-json-pointer-01 
  53  # 
  54  # 1: integer offset 
  55  #    and 3:  fetch key 
  56  #    and 4:  fetch node @relpath 
  57  #    and 5:  offset only - top document 
  58  # 
  59  # else: error 
  60  # 
  61  if V3K: 
  62      _RELPOINTER = re.compile(r"(0|[1-9][0-9]*)(([#]$)|(/.*$)|($))")  # 1 +(3 | 4 | 5) 
  63  else: 
  64      _RELPOINTER = re.compile(unicode(r"(0|[1-9][0-9]*)(([#]$)|(/.*$)|($))"))  # 1 +(3 | 4 | 5) 
  65   
  66  #: Unescaped character in reference-token [RFC6901]_ 
  67  _unescaped = re.compile(r'/|~[^01]') 
  68   
  69   
  70  _privattr = { 
  71      'isfragment': '__isfragment', 
  72      'isrel': '__isrel', 
  73      'raw': '__raw', 
  74      'relupidx': '__relupidx', 
  75      'start': '__start', 
  76      'startrel': '__startrel', 
  77  } #: private attributes accessible by __steattr__ and __getattr__ only 
  78   
  79   
80 -def fetch_pointerpath(node, base, restype=M_FIRST):
81 """Converts the address of *node* within the data structure 82 *base* into a corresponding pointer path. 83 The current implementation is search based, thus 84 may cause performance issues when frequently applied, 85 or processing very large structures. 86 87 For example: :: 88 89 nodex = {'a': 'pattern'} 90 data = {0: nodex, 1: [{'x':[nodex}]]} 91 92 res = fetch_pointerpath(nodex, data) 93 94 res = [ 95 [0], 96 [1, 0, 'x', 0] 97 ] 98 99 Args: 100 101 **node**: 102 Address of Node to be searched for. 103 104 **base**: 105 A tree top node to search the subtree for node. 106 107 **restype**: 108 Type of search. :: 109 110 M_FIRST: The first match only. 111 M_LAST: The first match only. 112 M_ALL: All matches. 113 114 Returns: 115 116 Returns a list of lists, where the contained 117 lists are pointer pathlists for matched elements. 118 119 * restype:=M_FIRST: '[[<first-match>]]', 120 121 * restype:=M_LAST: '[[<last-match>]]', 122 123 * restype:=M_ALL: '[[<first-match>],[<second-match>],...]' 124 125 Raises: 126 127 JSONDataError 128 129 """ 130 if not node or not base: 131 return [] 132 133 if isinstance(base, JSONData): 134 base = base.data 135 136 spath = [] 137 res = [] 138 139 kl = 0 140 141 if type(base) is list: # first layer - list of elements 142 kl = 0 143 if id(node) == id(base): # top node 144 res.append([kl]) 145 else: 146 for sx in base: 147 if id(node) == id(sx): 148 s = spath[:] 149 s.append(kl) 150 res.append(s) 151 152 elif type(sx) in (dict, list): 153 sublst = fetch_pointerpath(node, sx, restype) 154 if sublst: 155 for slx in sublst: 156 # TODO: update the documentation-scanner: res.append([kl, *slx]) 157 _l = [kl] 158 _l.extend(slx) 159 res.append(_l) 160 elif type(sx) in (tuple, set,): 161 raise JSONPointerError(sx) 162 kl += 1 163 164 elif type(base) is dict: # first layer - dict of elements 165 if id(node) == id(base): # top node 166 res.append(['']) 167 else: 168 for k, v in base.items(): 169 if id(node) == id(v): 170 spath.append(k) 171 res.append(spath) 172 continue 173 elif type(v) in (list, dict): 174 sublst = fetch_pointerpath(node, v, restype) 175 if sublst: 176 for slx in sublst: 177 if slx: 178 # TODO: update the documentation-scanner: res.append([k, *slx]) 179 _l = [k] 180 _l.extend(slx) 181 res.append(_l) 182 elif type(v) in (tuple, set,): 183 raise JSONPointerError(v) 184 185 elif type(base) in (tuple, set,): 186 raise JSONPointerError(base) 187 188 if res and restype == M_FIRST: 189 return [res[0]] 190 elif res and restype == M_LAST: 191 return [res[-1]] 192 return res
193 194
195 -class JSONPointer(list):
196 """Represents exactly one JSONPointer in compliance with 197 IETF RFC6901 and relative-pointer/draft-1/2018 198 """ 199 VALID_INDEX = re.compile('0|[1-9][0-9]*$') 200 """Regular expression for valid numerical index.""" 201
202 - def __init__(self, ptr, **kargs):
203 """Normalizes and stores a JSONPointer. The internal 204 representation depends on the type. 205 206 * absolute path: 207 208 A list of ordered items representing 209 the path items. 210 211 * relative path: 212 213 Relative paths in addition provide 214 a positive numeric offset of outer 215 containers [RELPOINTER]_. 216 217 Processes the ABNF of a JSON Pointer from RFC6901 218 and/or a relative JSON Pointer(draft 2018). 219 220 Attributes: 221 For details see manuals. 222 223 * *isfragment* 224 * *isrel* 225 * *raw* 226 * *start* 227 * *startrel* 228 229 Args: 230 **ptr**: 231 A JSONPointer to be represented by this object. The 232 supported formats are: 233 234 .. parsed-literal:: 235 236 ptr := ( 237 JSONPointer # [RFC6901]_ or [RELPOINTER]_ 238 | <rfc6901-string> # [RFC6901]_ 239 | <relative-pointer-string> # [RELPOINTER]_ 240 | <pointer-items-list> # non-URI-fragment pointer path items of [RFC6901]_ 241 ) 242 243 JSONPointer: 244 A valid object, is copied into this object, 245 see 'deep'. Supports *rfc6901* [RFC6901]_ 246 and *relative* pointers [RELPOINTER]_. 247 248 *rfc6901-string*: 249 A string i accordance to RFC6901 [RFC6901]_. 250 251 *relative-pointer-string*: 252 Draft standard, currently 253 experimental [RELPOINTER]_. 254 255 *pointer-items-list*: 256 Expects a path list, where each item 257 is processed for escape and unquote. 258 Supports *rfc6901* pointers [RFC6901]_. 259 260 Containing: 261 262 * absolute JSON Pointer 263 * relative JSON Pointer, requires the 264 keyword argument *startrel* 265 266 kargs: 267 268 **debug**: 269 Enable debugging. 270 271 **deep**: 272 Applies for copy operations on structured data 273 'deep' when 'True', else 'shallow' only. 274 Flat data types are copied by value in any case. 275 276 **node**: 277 Force to set the pointed node in the internal cache. 278 279 **replace**: 280 Replace masked characters, is applied onto the *ptr* 281 parameter only. For the replacement of *startrel* 282 create and pass an object *JSONPointer*. :: 283 284 replace := ( 285 True # replaces rfc6901 escape sequences: ~0 and ~1 286 | False # omit unescaping 287 ) 288 289 .. note:: 290 291 Match operations on address strings are proceeded literally, 292 thus the escaped characters should be consistent, 293 see rfc6901, Section 3. 294 295 default := False 296 297 **startrel**: 298 Start node for relative JSON Pointers. Is evaluated 299 only in combination with a relative path, else 300 ignored. :: 301 302 startrel := ( 303 JSONPointer # supports [RFC6901]_ and [RELPOINTER]_ 304 | <rfc6901-string> # supports [RFC6901]_ 305 | <rel-pointer-string> # supports only relative to whole-document '0/...' 306 ) 307 308 default := "" # whole document 309 310 Returns: 311 When successful returns *True*, else returns either *False*, or 312 raises an exception. 313 Success is the complete addition only, thus one failure returns 314 *False*. 315 316 Raises: 317 JSONPointerError: 318 319 """ 320 321 self.debug = kargs.get('debug', False) 322 self.deep = deep = kargs.get('deep', False) 323 self.node = kargs.get('node', None) # cache for reuse 324 self.__startrel = kargs.get('startrel', '') # default whole document 325 replace = kargs.get('replace', False) 326 327 super(JSONPointer, self).__init__() 328 329 if type(ptr) in (int, float): # pointer are unicode only 330 ptr = unicode(ptr) # is relative pointer 331 self.__raw = ptr 332 333 elif deep: 334 if type(ptr) in ISSTR: 335 self.__raw = ptr[:] 336 else: 337 self.__raw = copy.deepcopy(ptr) 338 else: 339 if type(ptr) in ISSTR: 340 self.__raw = ptr 341 elif isinstance(ptr, JSONPointer): 342 self.__raw = JSONPointer(ptr.get_raw(), startrel=ptr.get_startrel()) 343 else: 344 self.__raw = copy.copy(ptr) 345 346 self.__isrel = False #: marks a relative pointer 347 self.__start = None 348 self.__isfragment = False #: marks a uri fragment 349 350 if type(ptr) in (list, tuple): # no-fragment rfc6901 351 self.extend(ptr) 352 return 353 354 if ptr in('', '#'): # shortcut for whole document, see RFC6901 355 # '#' is URI fragments RFC6901 section 6 356 return None 357 358 elif ptr == '/': # shortcut for empty tag at top-level, see RFC6901 359 self.append('') 360 return None 361 362 elif isinstance(ptr, ISSTR): # string in accordance to RFC6901 363 if ptr == '#/': # URI fragments RFC6901 section 6 364 self.__isfragment = True 365 self.append('') 366 return None 367 368 elif ptr.startswith('#'): # URI fragments RFC6901 section 6 369 self.__isfragment = True 370 ptr = url_unquote(ptr[1:]) 371 372 elif ptr[0].isdigit(): # relative pointer - 2018/draft1 see doc [RELPOINTER] 373 374 # 375 # 1: integer offset 376 # 3: fetch key 377 # 4: fetch node @relpath 378 # 5: int 379 # 380 _m = _RELPOINTER.match(ptr) 381 if _m is None: 382 raise JSONPointerError("Syntax:" + str(ptr)) 383 self.__isrel = True 384 385 if type(self.__startrel) not in (JSONPointer, list, str, unicode): 386 _sr = str(type(self.__startrel)) + " / " + str(self.__startrel) 387 if len(_sr) >200: 388 _sr = _sr[:200] 389 raise JSONPointerError("type not supported startrel=" + _sr) 390 391 # 392 # <int><jsonpointer> or <int># 393 # 394 self.__relupidx = int(_m.group(1)) # <int> - upward increment 395 if self.__relupidx == None: 396 raise JSONPointerError("Cannot scan:" + str(ptr)) 397 398 if _m.group(4): # <int><jsonpointer> 399 ptr = _m.group(4) # downward path 400 self.__isrelpathrequest = True 401 402 elif _m.group(3): # fetch-key/fetch-index 403 ptr = None 404 self.__isrelpathrequest = False 405 406 elif _m.group(5) != None: # <int> - whole rel-sub-document 407 ptr = None 408 self.__isrelpathrequest = True 409 else: 410 raise JSONPointerError("Cannot scan:" + str(ptr)) 411 412 if type(self.__startrel) in ISSTR or type(self.__startrel) is list: 413 self.__startrel = JSONPointer(self.__startrel) 414 415 if len(self.__startrel) < self.__relupidx: # integer prefix-overflow 416 raise JSONPointerError( 417 "\ninteger prefix overflow:" 418 + "\n prefix = " + str(self.__relupidx) 419 + "\n len(startrel) = " + str(len(self.__startrel)) 420 + "\n startrel = " + str(self.__startrel) 421 ) 422 423 elif len(self.__startrel) == self.__relupidx: # integer-prefix equel to offset 424 if self.__isrelpathrequest: # anchor for a relative path 425 self.__start = JSONPointer(self.__startrel[:(len(self.__startrel)-self.__relupidx)]) 426 else: # request for key/index 427 raise JSONPointerError( 428 "\nkey/index request for <whole-document> prohibited by specification [RELPOINTER], see manuals:" 429 + "\n prefix = " + str(self.__relupidx) 430 + "\n len(startrel) = " + str(len(self.__startrel)) 431 + "\n startrel = " + str(self.__startrel) 432 ) 433 434 435 elif len(self.__startrel) > self.__relupidx: # integer-prefix within offset 436 self.__start = JSONPointer(self.__startrel[:(len(self.__startrel)-self.__relupidx)]) 437 438 else: 439 self.__start = JSONPointer('') 440 441 if ptr is not None: 442 self.extend(ptr.split('/')) 443 444 if len(self) == 1 or self[0] != '': 445 raise JSONPointerTypeError("requires a valid JSON pointer: " + str(ptr)) 446 447 self.pop(0) 448 449 elif isinstance(ptr, JSONPointer): # copy constructor 450 if ptr.isrel(): 451 self.__isrel = ptr.isrel() 452 self.__relupidx = ptr.get_relupidx() 453 self.__isrelpathrequest = ptr.isrelpathrequest() 454 455 if deep: 456 self.__raw = ptr.get_raw()[:] 457 if ptr.isrel(): 458 self.__start = ptr.get_start().copy() 459 self.__startrel = ptr.get_startrel().copy() 460 self.extend(ptr.copy_path()) 461 else: 462 self.__raw = ptr.get_raw() 463 if ptr.isrel(): 464 self.__start = ptr.get_start().copy() 465 self.__startrel = ptr.get_startrel().copy() 466 self.extend(ptr) 467 468 elif type(ptr) is list: 469 # list of entries in accordance to RFC6901, and JSONPointer 470 471 def presolv(p0): 472 if isinstance(p0, JSONPointer): # copy constructor 473 return p0.ptr 474 elif p0 in ('', '/'): 475 return p0 476 elif type(p0) in (str, unicode): 477 return p0 478 elif type(p0) in (int, float): 479 return str(p0) 480 else: 481 raise JSONPointerError("Invalid nodepart:" + str(p0)) 482 return p0
483 484 if deep: 485 self.extend(map(lambda s: s[:], ptr)) 486 else: 487 self.extend(map(presolv, ptr)) 488 self.__raw = '/' + '/'.join(self) 489 490 else: 491 if not ptr: 492 self.__raw = None 493 return None 494 raise JSONPointerError("Pointer type not supported:", 495 type(ptr)) 496 497 def _rep0(x): 498 if type(x) in (str, unicode): 499 if x.isdigit(): 500 return int(x) 501 return url_unquote(x).replace('~1', '/').replace('~0', '~') 502 return x
503 504 def _rep1(x): 505 if type(x) in (str, unicode): 506 if x.isdigit(): 507 return int(x) 508 return x 509 510 if replace: 511 sx = [_rep0(x) for x in self] 512 else: 513 sx = [_rep1(x) for x in self] 514 515 del self[:] 516 self.extend(sx) 517
518 - def __add__(self, x):
519 """Appends a Pointer to self. 520 521 Args: 522 **x**: 523 A valid JSONPointer of type: :: 524 525 x := ( 526 JSONPointer - fragment 527 | JSONPointer - relative-pointer 528 | relative pointer 529 ) 530 531 Returns: 532 A new object of JSONPointer 533 534 Raises: 535 JSONPointerError: 536 537 """ 538 ret = JSONPointer(self) 539 # pointer are unicode only, RFC6901/RFC3829 540 try: 541 if type(x) in (str, unicode) and x[0] is '#': 542 x = x[1:] 543 except IndexError: 544 pass 545 546 if x == '': # whole document, RFC6901 547 pass 548 elif x == u'/': # empty tag 549 ret.__raw += x 550 ret.append('') 551 elif isinstance(x, JSONPointer): 552 ret.__raw += x.raw 553 ret.extend(x) 554 elif type(x) in (list, tuple,): 555 ret.__raw += u'/' + u'/'.join(x) 556 ret.extend(x) 557 elif type(x) in (str, unicode): 558 if x[0] == u'/': 559 ret.extend(x[1:].split('/')) 560 ret.__raw += x 561 else: 562 ret.extend(x.split('/')) 563 ret.__raw += u'/' + x 564 elif type(x) is int: 565 ret.append(x) 566 ret.__raw += u'/' + unicode(x) 567 elif x is None: 568 return ret 569 570 else: 571 raise JSONPointerError() 572 return ret
573
574 - def __call__(self, x, *args, **kargs):
575 """Evaluates the pointer value on the document. 576 577 Args: 578 **x**: 579 A valid JSON document. 580 581 Returns: 582 The resulting pointer value. 583 584 Raises: 585 JSONPointerError 586 """ 587 return self.evaluate(x, *args, **kargs)
588
589 - def __delattr__(self, name):
590 raise NotImplementedError("delete: " + name)
591
592 - def __eq__(self, x):
593 """Compares this pointer with x. 594 595 Args: 596 **x**: 597 A JSONPointer object. 598 599 Returns: 600 True or False 601 602 Raises: 603 JSONPointerError 604 """ 605 if isinstance(x, JSONPointer): 606 return self.get_pointer_str(NOTATION_JSON) == x.get_pointer_str(NOTATION_JSON) 607 elif type(x) == list: 608 if not x: 609 return self.get_pointer_str(NOTATION_JSON) == u'' 610 return self.get_pointer_str(NOTATION_JSON) == u'/' + u'/'.join(map(unicode, x)) 611 elif type(x) in (str, unicode): 612 return self.get_pointer_str(forcenotation=NOTATION_JSON) == x 613 elif type(x) is int: 614 return self.get_pointer_str(forcenotation=NOTATION_JSON) == u'/' + unicode(x) 615 elif x is None: 616 return False 617 else: 618 raise JSONPointerError() 619 return False
620
621 - def __ge__(self, x):
622 """Checks containment(>=) of another pointer within this. 623 624 The weight of contained entries is the criteria, though 625 the shorter is the bigger. This is true only in case of 626 a containment relation. 627 628 The number of equal path pointer items is compared. 629 630 Args: 631 **x**: 632 A valid Pointer. 633 634 Returns: 635 True or False 636 637 Raises: 638 JSONPointerError: 639 640 """ 641 if isinstance(x, JSONPointer): 642 return super(JSONPointer, self).__le__(x) 643 elif type(x) in ISSTR: 644 return super(JSONPointer, self).__le__(JSONPointer(x)) 645 else: 646 raise JSONPointerError()
647
648 - def __gt__(self, x):
649 """Checks containment(>) of another pointer or object within this. 650 651 The number of equal items is compared. 652 653 Args: 654 **x**: 655 A valid Pointer. 656 657 Returns: 658 True or False 659 660 Raises: 661 JSONPointerError: 662 """ 663 if isinstance(x, JSONPointer): 664 return super(JSONPointer, self).__gt__(x) 665 elif type(x) in ISSTR: 666 return super(JSONPointer, self).__lt__(JSONPointer(x)) 667 else: 668 raise JSONPointerError()
669
670 - def __iadd__(self, x):
671 """Add in place x to self, appends a path. 672 673 Args: 674 **x**: 675 A valid Pointer. 676 677 Returns: 678 'self' with updated pointer attributes 679 680 Raises: 681 JSONPointerError: 682 """ 683 if type(x) == list: 684 self.__raw += unicode('/' + '/'.join(x)) 685 self.extend(x) 686 elif isinstance(x, JSONPointer): 687 if x.raw[0] != u'/': 688 self.__raw += u'/' + x.raw 689 else: 690 self.__raw = x.raw 691 self.extend(x) 692 elif type(x) is int: 693 self.append(unicode(x)) 694 self.__raw += u'/' + unicode(x) 695 elif x == '': # whole document, RFC6901 696 raise JSONPointerError("Cannot add the whole document") 697 elif x == u'/': # empty tag 698 self.__raw += x 699 self.append('') 700 elif type(x) in (str, unicode): 701 if x[0] == u'/': 702 self.extend(x[1:].split('/')) 703 self.__raw += x 704 else: 705 self.extend(x.split('/')) 706 self.__raw += u'/' + x 707 elif x is None: 708 return self 709 710 else: 711 raise JSONPointerError() 712 return self
713
714 - def __le__(self, x):
715 """Checks containment(<=) of this pointer within another. 716 717 The number of equal items is compared. 718 719 Args: 720 **x**: 721 A valid Pointer. 722 723 Returns: 724 True or False 725 726 Raises: 727 JSONPointerError: 728 """ 729 if isinstance(x, JSONPointer): 730 return super(JSONPointer, self).__ge__(x) 731 elif type(x) in ISSTR: 732 return super(JSONPointer, self).__ge__(JSONPointer(x)) 733 else: 734 raise JSONPointerError()
735
736 - def __lt__(self, x):
737 """Checks containment(<) of this pointer within another. 738 739 The number of equal items is compared. 740 741 Args: 742 **x**: 743 A valid Pointer. 744 745 Returns: 746 True or False 747 748 Raises: 749 JSONPointerError: 750 """ 751 if isinstance(x, JSONPointer): 752 return super(JSONPointer, self).__gt__(x) 753 elif type(x) in ISSTR: 754 return super(JSONPointer, self).__gt__(JSONPointer(x)) 755 else: 756 raise JSONPointerError()
757
758 - def __ne__(self, x):
759 """Compares this pointer with x. 760 761 Args: 762 **x**: 763 A valid Pointer. 764 765 Returns: 766 True or False 767 768 Raises: 769 JSONPointerError 770 """ 771 return not self.__eq__(x)
772
773 - def __radd__(self, x):
774 """Adds itself as the right-side-argument to the left. 775 776 This method appends 'self' to a path fragment on the left. 777 Therefore it adds the path separator on it's left side only. 778 The left side path fragment has to maintain to be in 779 accordance to RFC6901 by itself. 780 781 Once 'self' is added to the left side, it terminates it's 782 life cycle. Thus another simultaneous add operation is 783 handled by the resulting other element. 784 785 Args: 786 **x**: 787 A valid Pointer. 788 789 Returns: 790 The updated input of type 'x' as 'x+S(x)' 791 792 Raises: 793 JSONPointerError: 794 """ 795 if x == '': # whole document, RFC6901 796 return u'/' + u'/'.join(map(unicode, self)) 797 elif x == u'/': # empty tag 798 return x + u'/' + u'/'.join(map(unicode, self)) 799 elif type(x) is int: 800 return u'/' + unicode(x) + u'/' + u'/'.join(map(unicode, self)) 801 elif type(x) in (str, unicode): 802 return x + u'/' + u'/'.join(map(unicode, self)) 803 elif type(x) == list: 804 return x.extend(self) 805 else: 806 raise JSONPointerError() 807 return x
808
809 - def __repr__(self):
810 """Returns the attribute self.__raw, which is the raw input JSONPointer. 811 812 Args: 813 None 814 815 Attributes: 816 Evaluates *self.__isrel* 817 818 Returns: 819 For relative paths: :: 820 (<start-offset>, <pointer>) 821 822 start-offset := [<self.startrel>] 823 pointer := [<self>] 824 825 For RFC6901 paths: :: 826 827 <pointer> 828 829 pointer := [<self>] 830 831 Raises: 832 pass-through 833 834 """ 835 if self.__isrel: 836 ret = '(%s, %s)' % ( 837 repr(self.__start), 838 unicode(super(JSONPointer, self).__repr__()), 839 ) 840 841 else: 842 ret = super(JSONPointer, self).__repr__() 843 844 if ret == '': 845 return "''" 846 return ret
847
848 - def __setattr__(self, name, value):
849 try: 850 self.__dict__[_privattr[name]] = value 851 except KeyError: 852 self.__dict__[name] = value
853
854 - def __str__(self):
855 """Returns the string for the processed path. 856 857 Args: 858 None 859 860 Attributes: 861 Evaluates *self.__isrel* 862 863 Returns: 864 For relative paths: :: 865 (<start-offset>, <pointer>) 866 867 start-offset := [<self.startrel>] 868 pointer := [<self>] 869 870 For RFC6901 paths: :: 871 872 <pointer> 873 874 pointer := [<self>] 875 876 Raises: 877 pass-through 878 879 """ 880 881 if self.__isrel: 882 if self.__start: 883 ret = "%s" % ( 884 '/' + '/'.join((str(x) for x in self.__start)) + '/' + '/'.join((str(x) for x in self)) 885 ) 886 elif self: 887 ret = "%s" % ('/' + '/'.join((str(x) for x in self))) 888 else: 889 ret = '""' 890 891 else: 892 if self: 893 ret = "%s" % ('/' + '/'.join((str(x) for x in self))) 894 else: 895 ret = '""' 896 897 if ret == '': 898 return "''" 899 return ret
900
901 - def check_node_or_value(self, jsondata, parent=False):
902 """Checks the existence of the corresponding node 903 within the JSON document. 904 905 Args: 906 **jsondata**: 907 A valid JSON data node. 908 909 **parent**: 910 If *True* returns the parent node of the pointed value. 911 912 Returns: 913 True or False 914 915 Raises: 916 JSONPointerError: 917 918 pass-through 919 """ 920 if self == []: # special RFC6901, whole document 921 return not (not jsondata) 922 elif self == ['']: # special RFC6901, '/' empty top-tag 923 try: 924 return not ( not jsondata['']) 925 except KeyError: 926 return False 927 928 if not self.isvalid_nodetype(jsondata): 929 # concrete info for debugging for type mismatch 930 raise JSONPointerError("Invalid nodetype parameter:" + 931 str(type(jsondata))) 932 933 if parent: 934 _s = self[:-1] 935 else: 936 _s = self 937 for x in _s: 938 if isinstance(jsondata, dict): 939 jsondata = jsondata.get(x, False) 940 if not jsondata: 941 return False 942 elif isinstance(jsondata, list): 943 jsondata = jsondata[x] 944 if not jsondata: 945 return False 946 947 if not self.isvalid_nodetype(jsondata): 948 # concrete info for debugging for type mismatch 949 raise JSONPointerError("Invalid path nodetype:" + 950 str(type(jsondata))) 951 self.node = jsondata # cache for reuse 952 return True
953
954 - def copy(self, **kargs):
955 """Creates a copy of self. 956 957 Args: 958 None 959 960 kargs: 961 **deep**: 962 When *True* creates a deep copy, 963 else shallow. 964 965 Returns: 966 A copy of self. 967 968 Raises: 969 pass-through 970 """ 971 return JSONPointer(self, copydata=kargs.get('deep', C_DEEP))
972
973 - def copy_path_list(self, parent=False):
974 """Returns a deep copy of the objects pointer path list. 975 976 Args: 977 **parent**: 978 The parent node of the pointer path. 979 980 Returns: 981 A copy of the path list. 982 983 Raises: 984 none 985 """ 986 if self == []: # special RFC6901, whole document 987 return [] 988 if self == ['']: # special RFC6901, '/' empty top-tag 989 return [''] 990 991 if parent: 992 return [ s[:] for s in self[:-1]] 993 else: 994 return [ s[:] for s in self[:]]
995 996 # if parent: 997 # return map(lambda s: s[:], self[:-1]) 998 # else: 999 # return map(lambda s: s[:], self[:]) 1000
1001 - def __deepcopy__(self, memo):
1002 # return JSONPointer(self[:], copydata=C_DEEP) 1003 return JSONPointer(self[:])
1004
1005 - def __getattr__(self, name):
1006 try: 1007 return self.__dict__[name] 1008 except KeyError as e: 1009 if V3K: 1010 raise JSONPointerError( 1011 "Unknown attribute: " + repr(e) 1012 ) # from None 1013 else: 1014 raise JSONPointerError( 1015 "Unknown attribute: " + repr(e) 1016 )
1017
1018 - def get_key(self):
1019 """Get the resulting key for the pointer. In case 1020 of a relative pointer as resulting from the processing 1021 of the relative pointer and the starting node. 1022 """ 1023 if not self.__isrel: # pointer in accordance to rfc6901 1024 if self == None: 1025 # data is not initialized at all - basically impossible - anyhow 1026 return None 1027 1028 elif not self: 1029 # whole document - RFC6901 1030 return '' 1031 1032 return self[-1] 1033 1034 # 1035 # relative draft-1/2018 1036 # 1037 if self.__isrelpathrequest: # is get relpath request 1038 if not self: # special - whole rel document - integer only 1039 if not self.__start: # no offset 1040 return None 1041 return str(self.__start[-1]) # resulting offset only 1042 return self[-1] # a valid pointer 1043 1044 else: # is get-key/index - request 1045 # self is None in any case 1046 if not self.__start: # the whole document 1047 return None 1048 1049 elif len(self.__start) - 1 - self.__relupidx <= 0: # the whole document - fs-like for index-overflow 1050 return None 1051 1052 return str(self.__start[len(self.__start) - 1 - self.__relupidx])
1053
1054 - def get_node_and_child(self, jsondata):
1055 """Returns a tuple containing the parent node and self as the child. 1056 1057 Args: 1058 **jsondata**: 1059 A valid JSON data node. 1060 1061 Returns: 1062 The the tuple: :: 1063 1064 (p, c): 1065 p: Node reference to parent container. 1066 c: Node reference to self as the child. 1067 1068 Raises: 1069 JSONPointerError: 1070 1071 pass=through 1072 """ 1073 n = self(jsondata, True) # get parent 1074 if len(self) == 1: 1075 return n, None 1076 try: 1077 return n, self(jsondata, False) 1078 except (IndexError, KeyError): 1079 return n, None 1080 raise JSONPointerError(self.__raw)
1081
1082 - def get_node_and_key(self, jsondata):
1083 """Returns a tuple containing the parent node and the key of current. 1084 1085 Args: 1086 **jsondata**: 1087 A valid JSON data node. 1088 1089 Returns: 1090 The the tuple: :: 1091 1092 (n, k): 1093 n: Node reference to parent container. 1094 k: Key for self as the child entry: 1095 1096 k := ( 1097 <list-index> 1098 | <dict-key> 1099 | None 1100 ) 1101 1102 list-index: 'int' 1103 dict-key: 'UTF-8' 1104 None: "for root-node" 1105 1106 Raises: 1107 JSONPointerError: 1108 1109 pass-through 1110 """ 1111 n = self(jsondata, True) 1112 if len(self) == 1 and self[0] == '': 1113 return n, None 1114 1115 try: 1116 return n, self[-1], 1117 except (IndexError, KeyError): 1118 return n, None 1119 raise JSONPointerError(self.__raw)
1120
1121 - def get_node_value(self, jsondata, cp=C_SHALLOW, **kargs):
1122 """Gets the copy of the corresponding node. 1123 Relies on the standard package 'json'. 1124 1125 Args: 1126 **jsondata**: 1127 A valid JSON data node. 1128 1129 **cp**: 1130 Type of returned copy. :: 1131 1132 cp := ( 1133 C_DEEP 1134 | C_REF 1135 | C_SHALLOW 1136 ) 1137 1138 kargs: 1139 **valtype**: 1140 Type of requested value. 1141 1142 Returns: 1143 The copy of the node, see option *copy*. 1144 1145 Raises: 1146 JSONPointerError 1147 1148 pass-through 1149 """ 1150 valtype = kargs.get('valtype', None) 1151 1152 _resroot = self.get_pointer(jsondata, superpose=True) 1153 if _resroot == None: # basically impossible - anyhow 1154 return None 1155 if not _resroot: # == [] : special RFC6901, whole document 1156 return jsondata 1157 elif len(_resroot) == 0: 1158 return jsondata # same as previous 1159 elif len(_resroot) == 1: 1160 if _resroot[0] == '': 1161 return jsondata # the whole document 1162 try: 1163 return jsondata[_resroot[0]] # 1164 except (KeyError, IndexError) as e: 1165 if V3K: 1166 raise JSONPointerError( 1167 "Node(" + str(self.index(0)) + "):" + str(self[0]) + " of " + str(self) + ":" + str(e) 1168 # 1169 # TODO: update doctool for python3 introspection 1170 # 1171 ) # from None 1172 else: 1173 raise JSONPointerError( 1174 "Node(" + str(self.index(0)) + "):" + str(self[0]) + " of " + str(self) + ":" + str(e) 1175 ) 1176 1177 if not self.isvalid_nodetype(jsondata): 1178 raise JSONPointerError("Invalid nodetype parameter:" + 1179 str(type(jsondata))) 1180 1181 try: 1182 for x in _resroot: 1183 if not isinstance(jsondata, (list, dict, JSONData)): 1184 break 1185 try: 1186 jsondata = jsondata[x] # want the exception 1187 except TypeError: 1188 jsondata = jsondata[int(x)] # still want the exception 1189 1190 except Exception as e: 1191 if V3K: 1192 raise JSONPointerError( 1193 "Node(" + str(self.index(x)) + "):" + str(x) + " of " + str(self) + ":" + str(e) 1194 # 1195 # TODO: update doctool for python3 introspection 1196 # 1197 ) # from None 1198 else: 1199 raise JSONPointerError( 1200 "Node(" + str(self.index(x)) + "):" + str(x) + " of " + str(self) + ":" + str(e) 1201 ) 1202 1203 if valtype: # requested value type 1204 # fix type ambiguity for numeric 1205 if valtype in (int, float): 1206 if jsondata.isdigit(): 1207 jsondata = int(jsondata) 1208 elif valtype in (int, float): 1209 if jsondata.isdigit(): 1210 jsondata = float(jsondata) 1211 1212 if not type(jsondata) is valtype: 1213 raise JSONPointerError("Invalid path value type:" + str( 1214 type(valtype)) + " != " + str(type(jsondata))) 1215 1216 else: # in general valid value types - RFC4729,RFC7951 1217 if not self.isvalid_nodetype(jsondata): 1218 raise JSONPointerError( 1219 "Invalid path nodetype:" 1220 + str(type(jsondata))) 1221 1222 # self.node = jsondata # cache for reuse 1223 1224 if type(jsondata) in (dict, list,): 1225 if cp == C_SHALLOW: # default 1226 return copy.copy(jsondata) 1227 elif cp == C_DEEP: 1228 return copy.deepcopy(jsondata) 1229 1230 # C_REF 1231 return jsondata
1232
1233 - def get_node(self, jsondata):
1234 """Returns the existing node for the pointer, 1235 calls transparently *JSONPointer.__call__*. 1236 1237 Args: 1238 **jsondata**: 1239 A valid JSON data node. 1240 1241 Returns: 1242 The node reference. 1243 1244 Raises: 1245 JSONPointerError: 1246 1247 pass-through 1248 1249 """ 1250 return self.__call__(jsondata)
1251
1252 - def get_node_exist(self, jsondata, parent=False):
1253 """Returns two parts, the exisitng node for valid part of 1254 the pointer, and the remaining part of the pointer for 1255 the non-existing sub-path. 1256 1257 This method works similar to the 'evaluate' method, whereas it 1258 handles partial valid path pointers, which may also include 1259 a '-' in accordance to RFC6902. 1260 1261 Therefore the non-ambiguous part of the pointer is resolved, 1262 and returned with the remaining part for a newly create. 1263 Thus this method is in particular foreseen to support the 1264 creation of new sub data structures. 1265 1266 The 'evaluate' method therefore returns a list of two elements, 1267 the first is the node reference, the second the list of the 1268 remaining path pointer components. The latter may be empty in 1269 case of a fully valid pointer. 1270 1271 1272 Args: 1273 **jsondata**: 1274 A valid JSON data node. 1275 1276 **parent**: 1277 Return the parent node of the pointed value. 1278 1279 Returns: 1280 The node reference, and the remaining part. 1281 ret:=(node, [<remaining-path-components-list>]) 1282 1283 Raises: 1284 JSONPointerError: 1285 forwarded from json 1286 """ 1287 if super(JSONPointer, self).__eq__([]): # special RFC6901, whole document 1288 return (jsondata, None) 1289 if super(JSONPointer, self).__eq__(['']): # special RFC6901, '/' empty top-tag 1290 return (jsondata[''], None) 1291 1292 if type(jsondata) not in (dict, list, JSONData): 1293 # concrete info for debugging for type mismatch 1294 raise JSONPointerError("Invalid nodetype parameter:" + 1295 str(type(jsondata))) 1296 remaining = None 1297 try: 1298 if parent: 1299 for x in self[:-1]: 1300 remaining = x 1301 # want the exception, the keys within the process has to match 1302 jsondata = jsondata[x] 1303 else: 1304 for x in self: 1305 remaining = x 1306 # want the exception, the keys within the process has to match 1307 jsondata = jsondata[x] 1308 except Exception: 1309 if parent: 1310 remaining = self[self.index(remaining):-1] 1311 else: 1312 remaining = self[self.index(remaining):] 1313 else: 1314 remaining = None 1315 1316 if type(jsondata) not in (dict, list): 1317 # concrete info for debugging for type mismatch 1318 raise JSONPointerError("Invalid path nodetype:" + 1319 str(type(jsondata))) 1320 self.node = jsondata # cache for reuse 1321 return (jsondata, remaining,)
1322
1323 - def get_path_list(self):
1324 """Gets for the corresponding path list of the object pointer for 1325 in-memory access on the data of the 'json' package. 1326 1327 Args: 1328 none 1329 1330 Returns: 1331 The path list. 1332 1333 Raises: 1334 none 1335 """ 1336 if __debug__: 1337 if self.debug: 1338 print(repr(self)) 1339 return list(self)
1340
1341 - def get_path_list_and_key(self):
1342 """Gets for the corresponding path list of the object pointer for in-memory access on the data of the 'json' package. 1343 1344 Args: 1345 none 1346 1347 Returns: 1348 The path list. 1349 1350 Raises: 1351 none 1352 """ 1353 if len(self) > 2: 1354 return self[:-1], self[-1] 1355 elif len(self) == 1: 1356 return [], self[-1] 1357 elif len(self) == 0: 1358 return [], None
1359
1360 - def get_pointer(self, jsondata=None, **kargs):
1361 """Gets the object pointer in compliance to RFC6901 1362 or relative pointer/draft-01/2018. 1363 1364 The result is by default the assigned pointer itself without 1365 verification. Similar in case of a relative pointer the start 1366 offset is ignored by default and no verification is performed. 1367 1368 The following options modify this behaviour: 1369 1370 * superpose - superposes the *startrel* offset with the pointer 1371 * verify - verifies the actual existence of the nodes and/or 1372 intermediate nodes 1373 1374 The options could be applied combined. 1375 1376 Args: 1377 kargs: 1378 **returntype**: 1379 Defines the return type. :: 1380 1381 returntype := ( 1382 RT_DEFAULT | 'default' 1383 | RT_LST | 'list' | list 1384 | RT_JSONPOINTER | 'jpointer' 1385 ) 1386 1387 **superpose**: 1388 Is only relevant for relative paths. Superposes the offset 1389 *startrel* with the pointer into the resulting final pointer. 1390 By default nodes are not verified, see *verify* parameter. 1391 1392 default := True 1393 1394 **verify**: 1395 Verifies the "road" of the superposed pointers. :: 1396 1397 verify := ( 1398 V_DEFAULT | 'default' 1399 | V_NONE | 'none' | None # no checks at all 1400 | V_FINAL | 'final' # checks final result only 1401 | V_STEPS | 'steps' # checks each intermediate directory 1402 ) 1403 1404 default := None 1405 Returns: 1406 The new pointer in a choosen format, see *returntype*. 1407 1408 Raises: 1409 none 1410 """ 1411 returntype = rtypes2num[kargs.get('returntype')] 1412 verify = verify2num[kargs.get('verify')] 1413 1414 superpose = kargs.get('superpose', True) 1415 1416 # returned pointer 1417 _ptr = JSONPointer(self) 1418 1419 if self.__isrel: 1420 if superpose: 1421 # - superpose the rfc6901 and the startrel pointers 1422 1423 # 1424 # first calculate by ignoring jsondata, 1425 # but checks consistency of integer-prefix 1426 # and start-node-pointer 1427 # 1428 1429 # depth from pointer root to start with __startrel, 1430 # calc whether int-prefix larger than the length of the offset 1431 ix = len(self.__startrel) - self.__relupidx 1432 if ix < 0: 1433 raise JSONPointerError( 1434 "offset is shorter than integer-index startrel:%s - int-idx=%s" % ( 1435 len(self.__startrel), self.__relupidx) 1436 ) 1437 1438 if ix == 0: 1439 _ptr = self 1440 1441 else: 1442 _ptr = self.__startrel[:ix] 1443 _ptr.extend(self) 1444 1445 # 1446 # now perform optional verification 1447 # 1448 1449 if verify & V_FINAL: 1450 # check final only 1451 _chk = JSONPointer(_ptr)(jsondata) 1452 1453 elif verify & V_STEPS: 1454 # 1455 # check intermediate anchors by stepwise verification 1456 # 1457 1458 # check offset pointer , and get node 1459 _chk = JSONPointer(self.__startrel)(jsondata) 1460 1461 # check int-prefix ends within document, and get node 1462 _chk = JSONPointer(self.__relupidx + '#')(_chk) 1463 1464 # check pointer ends within document, and get node 1465 _chk = JSONPointer(self)(_chk) 1466 1467 # 1468 # cast return type 1469 # 1470 if returntype & RT_JSONPOINTER: 1471 return _ptr 1472 elif returntype & RT_LST: 1473 return list(_ptr) 1474 else: 1475 raise JSONPointerError("Unknown return type: " + str(returntype))
1476
1477 - def get_pointer_and_key(self, jsondata=None, **kargs):
1478 """Get the resulting pointer and key from the processing of 1479 the pointer and the optional starting node *stratrel*. 1480 """ 1481 ret = self.get_pointer(jsondata, **kargs) 1482 k = ret.pop() 1483 return (ret, k,)
1484
1485 - def get_pointer_str(self, jsondata=None, **kargs):
1486 """Gets the objects pointer string in compliance to RFC6901 1487 or relative pointer/draft-01/2018. 1488 1489 The result is by default the assigned pointer itself without 1490 verification. Similar in case of a relative pointer the start 1491 offset is ignored by default and no verification is performed. 1492 1493 The following options modify this behaviour: 1494 1495 * superpose - superposes the *startrel* offset with the pointer 1496 * verify - verifies the actual existence of the nodes and/or 1497 intermediate nodes 1498 1499 The options could be applied combined. 1500 1501 Args: 1502 kargs: 1503 **forcenotation**: 1504 Force the output notation for string representation to: :: 1505 1506 forcenotation := ( 1507 NOTATION_NATIVE # original format with unescape 1508 | NOTATION_JSON # transform to resulting pointer 1509 | NOTATION_HTTP_FRAGMENT # return a fragment with encoding 1510 | NOTATION_JSON_REL # resulting relative pointer 1511 | NOTATION_RAW # raw input 1512 ) 1513 1514 default := NOTATION_NATIVE 1515 1516 **REMINDER**: Applicable for return type string only. 1517 1518 **superpose**: 1519 Is only relevant for relative paths. Superposes the offset 1520 *startrel* with the pointer into the resulting final pointer. 1521 By default nodes are not verified, see *verify* parameter. 1522 1523 Returns: 1524 The new pointer in a choosen format, see *returntype*. 1525 1526 Raises: 1527 none 1528 """ 1529 forcenotation = kargs.get('forcenotation') 1530 superpose = kargs.get('superpose', False) 1531 1532 # 1533 # notation is only relevant for string representation, 1534 # while the internal technical representation is almost neutral 1535 # 1536 if forcenotation == NOTATION_JSON: 1537 if self.__isrel: 1538 if self.__isrelpathrequest: 1539 ret = self.__start.copy() 1540 else: 1541 return self.__start + '#' 1542 else: 1543 if super(JSONPointer,self).__eq__([]): 1544 return '""' 1545 ret = '/' 1546 1547 elif forcenotation == NOTATION_HTTP_FRAGMENT: 1548 if self.__isrel: 1549 if self.__isrelpathrequest: 1550 ret = self.__start 1551 else: 1552 return self.__start 1553 else: 1554 return '/' 1555 1556 elif forcenotation == NOTATION_JSON_REL: 1557 if self.__isrel: 1558 if self.__isrelpathrequest: 1559 ret = self.__start 1560 else: 1561 return self.__start 1562 else: 1563 return '/' 1564 1565 else: # NOTATION_NATIVE 1566 # 1567 # set the appropriate prefix: 1568 # rfc6901-pointer, rfc6901-uri-fragment-pointer, 1569 # relpath-draft-1 1570 # 1571 if self.__isfragment: # rfc6901 - uri-fragment 1572 if not self: # special - whole document 1573 ret = '#' 1574 else: # a subpath 1575 ret = '#/' 1576 1577 elif self.__isrel: # relpointer 1578 if self.__isrelpathrequest: # is get relpath request 1579 if superpose: 1580 ix = len(self.__startrel) - self.__relupidx 1581 if ix < 0: 1582 raise JSONPointerError( 1583 "offset is shorter than integer-index startrel:%s - int-idx=%s" % ( 1584 len(self.__startrel), self.__relupidx) 1585 ) 1586 1587 if ix == 0: 1588 _ptr = self 1589 1590 else: 1591 _ptr = self.__startrel[:ix] 1592 _ptr.extend(self) 1593 else: 1594 _ptr = self 1595 1596 if super(JSONPointer, self).__eq__([]): 1597 if _ptr: 1598 return unicode('/' + '/'.join(map(unicode, _ptr)) + '/') 1599 else: 1600 return unicode('""') 1601 else: 1602 return unicode('/' + '/'.join(map(unicode, _ptr))) 1603 1604 else: # is get-key/index - request 1605 return str(self.__relupidx) + '#' 1606 1607 else: # rfc6901 - in-document-absolute pointer 1608 if not self: # ==[] : special RFC6901, whole document 1609 return '' 1610 ret = '/' 1611 1612 if len(self) == 1 and self[0] == '': # special RFC6901, '/' empty top-tag 1613 return '/' 1614 1615 1616 return unicode(ret + '/'.join(map(unicode, self)))
1617
1618 - def get_raw(self):
1619 """Gets the objects raw 6901-pointer. 1620 1621 Args: 1622 none 1623 1624 Returns: 1625 The raw path. 1626 1627 Raises: 1628 none 1629 """ 1630 try: 1631 return self.__raw 1632 except KeyError: 1633 return None
1634
1635 - def get_relupidx(self):
1636 """Returns the resulting integer prefix. 1637 """ 1638 try: 1639 return self.__relupidx 1640 except KeyError: 1641 return None
1642
1643 - def get_start(self):
1644 """Returns the resulting start pointer after the 1645 application of the integer prefix. 1646 """ 1647 try: 1648 return self.__start 1649 except KeyError: 1650 return None
1651
1652 - def get_startrel(self):
1653 """Returns the raw start pointer. 1654 """ 1655 try: 1656 return self.__startrel 1657 except KeyError: 1658 return None
1659
1660 - def evaluate(self, jsondata, parent=False):
1661 """Gets the value resulting from the current pointer. 1662 1663 Args: 1664 **jsondata**: 1665 A JSON data node. :: 1666 1667 jsondata := ( 1668 JSONData 1669 | list 1670 | dict 1671 ) 1672 1673 **parent**: 1674 Return the parent node of the pointed value. 1675 When parent is selected, the pointed child node 1676 is not verified. 1677 1678 Returns: 1679 The referenced value. 1680 1681 Raises: 1682 JSONPointerError: 1683 1684 pass-through 1685 """ 1686 # 1687 # calc starting node 1688 # 1689 if self.__isrel: 1690 # relative draft-1/2018 1691 1692 # check whether pointed to a valid node 1693 if not self.__startrel.check_node_or_value(jsondata): 1694 raise JSONPointerError('node does not exist:"' + str(self.__startrel) + '"') 1695 1696 # pointer is getrelpath request 1697 if self.__isrelpathrequest: 1698 if self.__start: # already pre-calculated 1699 _startnode = self.__start.evaluate(jsondata) 1700 else: 1701 _startnode = jsondata 1702 1703 if not self: # special - whole rel document - integer only 1704 return _startnode 1705 1706 # pointer is get-key/index - request 1707 else: 1708 return self.__start.get_key() 1709 1710 elif self == []: 1711 # special RFC6901, whole document 1712 return jsondata 1713 1714 # 1715 # REMEMBER: special RFC6901, '/""' empty top-tag 1716 # section 5 / pg. 5 1717 # 1718 elif len(self) == 1 and self[0] == '': 1719 try: 1720 return jsondata[''] 1721 except KeyError as e: 1722 raise JSONPointerError("""Non-existent node(see RFC6901 - chap 5): '/""'""") 1723 1724 else: 1725 # a standard rfc6901 pointer 1726 _startnode = jsondata 1727 1728 if type(_startnode) not in (dict, list, JSONData): 1729 # concrete info for debugging for type mismatch 1730 if self.__isrel: 1731 1732 raise JSONPointerError( 1733 "Invalid nodetype parameter: %s" 1734 "\n pointer = %s" 1735 "\n startrel = %s" 1736 "\n start = %s" 1737 "\n jsondata = %s" 1738 "\n startnode = %s" 1739 % ( 1740 str(type(_startnode)), 1741 str(self), 1742 str(self.__startrel), 1743 str(self.__start), 1744 str(jsondata), 1745 str(_startnode), 1746 )) 1747 1748 else: 1749 raise JSONPointerError( 1750 "Invalid nodetype parameter:" + 1751 str(type(_startnode))) 1752 1753 try: 1754 if parent: 1755 for x in self[:-1]: 1756 # want the exception, the keys within the process has to match 1757 try: 1758 _startnode = _startnode[x] 1759 except TypeError as e: 1760 if x == '-': 1761 _startnode = _startnode[-1] 1762 else: 1763 try: 1764 # special python - reverse index 1765 _startnode = _startnode[int(x)] 1766 except ValueError: 1767 # now safe to give it up 1768 # raise e 1769 raise JSONPointerError(repr(e)) 1770 else: 1771 for x in self: 1772 try: 1773 # standard index 0<=x<infinite 1774 _startnode = _startnode[x] 1775 1776 except TypeError as e: 1777 if x == '-': 1778 # special rfc6901 1779 _startnode = _startnode[-1] 1780 else: 1781 try: 1782 # special python - reverse index 1783 _startnode = _startnode[int(x)] 1784 except ValueError: 1785 # now safe to give it up 1786 # raise e 1787 raise JSONPointerError(repr(e)) 1788 1789 except Exception as e: 1790 if V3K: 1791 if isinstance(_startnode, JSONData): 1792 _startnode = _startnode.data 1793 1794 if type(_startnode) is dict: 1795 _jdat = _startnode.keys() 1796 elif type(_startnode) is list: 1797 _jdat = '[0...' + str(len(_startnode) - 1) + ']' 1798 else: 1799 _jdat = _startnode 1800 1801 raise JSONPointerError( 1802 "Requires existing Node(jsondata[" + str(self.index(x)) + "]):" 1803 + '"' + str(self) + '":' + repr(e) + ':jsondata(keys/indexes)=' + str(_jdat) 1804 # 1805 # TODO: update doctool for python3 introspection before re-activation 1806 # 1807 ) # from None 1808 1809 else: 1810 raise JSONPointerError( 1811 "Requires existing Node(jsondata[" + str(self.index(x)) + "]):" 1812 + '"' + str(self) + '":' + repr(e) + ':jsondata=' + str(_startnode) 1813 ) 1814 1815 self.node = _startnode # cache for reuse 1816 return _startnode
1817
1818 - def isfragment(self):
1819 """Checks whether a http fragment.""" 1820 return self.__isfragment
1821
1822 - def isrelpathrequest(self):
1823 """Checks whether a path request.""" 1824 try: 1825 return self.__isrelpathrequest and self.__isrel 1826 except KeyError: 1827 return False
1828
1829 - def isrel(self):
1830 """Checks whether a relative pointer.""" 1831 return self.__isrel
1832
1833 - def isvalid_nodetype(self, x):
1834 """Checks valid node types of in-memory JSON data.""" 1835 return type(x) in VALID_NODE_TYPE_X or x == None
1836
1837 - def isvalrequest(self):
1838 """Checks whether a value request.""" 1839 return not self.__isvalrequest and self.__isrel
1840
1841 - def iter_path(self, jsondata=None, **kargs):
1842 """Iterator for the elements of the path pointer itself. 1843 1844 Args: 1845 **jsondata**: 1846 If provided a valid JSON data node, the 1847 path components are successively verified on 1848 the provided document. 1849 1850 kargs: 1851 **parent**: 1852 Uses the path pointer to parent node. 1853 1854 **rev**: 1855 Reverse the order, start with last. 1856 1857 **superpose**: 1858 Is only relevant for relative paths, for *rfc6901* defined 1859 paths the parameter is ignored. When *True* superposes 1860 the offset *startrel* with the pointer into the resulting 1861 final pointer. By default nodes are not verified, 1862 see *verify* parameter. :: 1863 1864 superpose := ( 1865 True # iterates resulting paths from *startrel* 1866 | False # iterates the path only 1867 ) 1868 1869 1870 default := True 1871 1872 Returns: 1873 Yields the iterator for the current path pointer 1874 components. 1875 1876 Raises: 1877 JSONPointerError: 1878 forwarded from json 1879 """ 1880 parent=kargs.get('parent', False) 1881 rev=kargs.get('rev', False) 1882 superpose=kargs.get('superpose', True) 1883 1884 _ptr = self.get_pointer(jsondata, superpose=superpose) 1885 1886 if self == []: # special RFC6901, whole document 1887 yield '' 1888 elif self == ['']: # special RFC6901, '/' empty top-tag 1889 yield '/' 1890 else: 1891 if rev: # reverse 1892 if parent: # for parent 1893 ptrpath = _ptr[:-1:-1] 1894 else: # full path 1895 ptrpath = _ptr[::-1] 1896 else: 1897 if parent: # for parent 1898 ptrpath = _ptr[:-1] 1899 else: # full path 1900 ptrpath = _ptr 1901 1902 try: 1903 x = ptrpath[0] 1904 for x in ptrpath: 1905 if jsondata: 1906 # want the exception, the keys within the process has to match 1907 jsondata = jsondata[x] 1908 yield x 1909 1910 except Exception as e: 1911 if V3K: 1912 raise JSONPointerError( 1913 "Node(" + str(ptrpath.index(x)) +"):" + str(x) + " of " + 1914 str(self) + ":" + repr(e) 1915 # 1916 # TODO: update doctool for python3 introspection 1917 # 1918 ) # from None 1919 else: 1920 raise JSONPointerError( 1921 "Node(" + str(ptrpath.index(x)) +"):" + str(x) + " of " + 1922 str(self) + ":" + repr(e) 1923 ) 1924 1925 self.node = jsondata # cache for reuse
1926
1927 - def iter_path_nodes(self, jsondata, parent=False, rev=False):
1928 """Iterator for the elements the path pointer points to. 1929 1930 Args: 1931 **jsondata**: 1932 A valid JSON data node. 1933 1934 **parent**: 1935 Uses the path pointer to parent node. 1936 1937 **rev**: 1938 Reverse the order, start with last. 1939 1940 Returns: 1941 Yields the iterator of the current node reference. 1942 1943 Raises: 1944 JSONPointerError: 1945 forwarded from json 1946 """ 1947 if self == []: # special RFC6901, whole document 1948 yield jsondata 1949 elif self == ['']: # special RFC6901, '/' empty top-tag 1950 yield jsondata[''] 1951 else: 1952 if rev: # reverse 1953 if parent: # for parent 1954 ptrpath = self[:-1:-1] 1955 else: # full path 1956 ptrpath = self[::-1] 1957 else: 1958 if parent: # for parent 1959 ptrpath = self[:-1] 1960 else: # full path 1961 ptrpath = self 1962 1963 try: 1964 x = ptrpath[0] 1965 for x in ptrpath: 1966 # want the exception, the keys within the process has to match 1967 jsondata = jsondata[x] 1968 yield jsondata 1969 except Exception as e: 1970 if V3K: 1971 raise JSONPointerError( 1972 "Node(" + str(ptrpath.index(x)) +"):" + str(x) + " of " + 1973 str(self) + ":" + repr(e) 1974 # 1975 # TODO: update doctool for python3 introspection 1976 # 1977 ) # from None 1978 else: 1979 raise JSONPointerError( 1980 "Node(" + str(ptrpath.index(x)) +"):" + str(x) + " of " + 1981 str(self) + ":" + repr(e) 1982 ) 1983 1984 self.node = jsondata # cache for reuse
1985
1986 - def iter_path_subpaths(self, jsondata=None, parent=False, rev=False):
1987 """Successive iterator for the resulting sub-paths the 1988 path pointer itself. 1989 1990 Args: 1991 **jsondata**: 1992 If provided a valid JSON data node, the 1993 path components are successively verified on 1994 the provided document. 1995 1996 **parent**: 1997 Uses the path pointer to parent node. 1998 1999 **rev**: 2000 Reverse the order, start with last. 2001 2002 Returns: 2003 Yields the iterator for the copy of the current 2004 slice of the path pointer. 2005 2006 Raises: 2007 JSONPointerError: 2008 forwarded from json 2009 """ 2010 if self == []: # special RFC6901, whole document 2011 yield '' 2012 elif self == ['']: # special RFC6901, '/' empty top-tag 2013 yield '/' 2014 else: 2015 curpath = [] 2016 if rev: # reverse 2017 if parent: # for parent 2018 ptrpath = self[:-1:-1] 2019 else: # full path 2020 ptrpath = self[::-1] 2021 else: 2022 if parent: # for parent 2023 ptrpath = self[:-1] 2024 else: # full path 2025 ptrpath = self 2026 2027 try: 2028 x = ptrpath[0] 2029 for x in ptrpath: 2030 if jsondata: 2031 # want the exception, the keys within the process has to match 2032 jsondata = jsondata[x] 2033 curpath.append(x) 2034 yield curpath[:] 2035 except Exception as e: 2036 if V3K: 2037 raise JSONPointerError( 2038 "Node(" + str(ptrpath.index(x)) +"):" + str(x) + " of " + 2039 str(self) + ":" + repr(e) 2040 # 2041 # TODO: update doctool for python3 introspection 2042 # 2043 ) # from None 2044 else: 2045 raise JSONPointerError( 2046 "Node(" + str(ptrpath.index(x)) +"):" + str(x) + " of " + 2047 str(self) + ":" + repr(e) 2048 )
2049
2050 - def iter_path_subpathdata(self, jsondata=None, parent=False, rev=False):
2051 """Successive iterator for the resulting sub-paths and the 2052 corresponding nodes. 2053 2054 Args: 2055 **jsondata**: 2056 If provided a valid JSON data node, the 2057 path components are successively verified on 2058 the provided document. 2059 2060 **parent**: 2061 Uses the path pointer to parent node. 2062 2063 **rev**: 2064 Reverse the order, start with last. 2065 2066 Returns: 2067 Yields the iterator for the tuple of the current 2068 slice of the path pointer and the reference of the 2069 corresponding node. :: 2070 2071 (<path-item>, <sub-path>, <node>) 2072 2073 path-item: copy of the item 2074 sub-path: copy of the current subpath 2075 node: reference to the corresponding node 2076 2077 Raises: 2078 JSONPointerError: 2079 forwarded from json 2080 """ 2081 if self == []: # special RFC6901, whole document 2082 yield '' 2083 elif self == ['']: # special RFC6901, '/' empty top-tag 2084 yield '/' 2085 else: 2086 curpath = [] 2087 if rev: # reverse 2088 if parent: # for parent 2089 ptrpath = self[:-1:-1] 2090 else: # full path 2091 ptrpath = self[::-1] 2092 else: 2093 if parent: # for parent 2094 ptrpath = self[:-1] 2095 else: # full path 2096 ptrpath = self 2097 2098 try: 2099 x = ptrpath[0] 2100 for x in ptrpath: 2101 if jsondata: 2102 # want the exception, the keys within the process has to match 2103 jsondata = jsondata[x] 2104 curpath.append(x) 2105 yield (x, curpath[:], jsondata) 2106 except Exception as e: 2107 if V3K: 2108 raise JSONPointerError( 2109 "Node(" + str(ptrpath.index(x)) +"):" + str(x) + " of " + 2110 str(self) + ":" + repr(e) 2111 # 2112 # TODO: update doctool for python3 introspection 2113 # 2114 ) # from None 2115 else: 2116 raise JSONPointerError( 2117 "Node(" + str(ptrpath.index(x)) +"):" + str(x) + " of " + 2118 str(self) + ":" + repr(e) 2119 ) 2120 2121 self.node = jsondata # cache for reuse
2122 2123 from jsondata.jsondata import JSONData 2124 VALID_NODE_TYPE_X = ( 2125 dict, 2126 list, 2127 str, 2128 unicode, 2129 int, 2130 float, 2131 bool, 2132 None, 2133 JSONData,) 2134 """Valid types of in-memory JSON node types for processing.""" 2135