Package sourceinfo :: Module helper

Source Code for Module sourceinfo.helper

  1  # -*- coding: utf-8 -*- 
  2  """pysourceinfo.helper - miscellaneous utilities - details see manuals 
  3   
  4  The provided interfaces are foreseen for the bottom layer of the software stack, 
  5  thus provide basic handling only. This includes special cases of the file system  
  6  parameters. For more advanced file system path processing refer to *filesysobjects*.  
  7  """ 
  8  from __future__ import absolute_import 
  9  from __future__ import print_function 
 10   
 11  import os 
 12  import sys 
 13  import re 
 14  from inspect import stack 
 15  from imp import PY_SOURCE, PY_COMPILED, C_EXTENSION, PKG_DIRECTORY, C_BUILTIN, PY_FROZEN 
 16   
 17  from pythonids import PYV35Plus, ISSTR 
 18   
 19  from sourceinfo import SourceInfoError, \ 
 20      presolve, P_FIRST, P_LAST, P_LONGEST, P_SHORTEST, P_IGNORE0, \ 
 21      OID_STR, OID_TUPLE, OID_LIST 
 22   
 23  if PYV35Plus: 
 24      # pylint: disable-msg=F0401 
 25      from importlib.machinery import SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES, OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES, EXTENSION_SUFFIXES  # @UnresolvedImport 
 26      from importlib import util  # @UnresolvedImport 
 27      # pylint: enable-msg=F0401 
 28  else: 
 29      from inspect import getmoduleinfo  # @UnresolvedImport 
 30   
 31   
 32  __author__ = 'Arno-Can Uestuensoez' 
 33  __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints" 
 34  __copyright__ = "Copyright (C) 2010-2017 Arno-Can Uestuensoez" \ 
 35      " @Ingenieurbuero Arno-Can Uestuensoez" 
 36  __version__ = '0.1.21' 
 37  __uuid__ = '9de52399-7752-4633-9fdc-66c87a9200b8' 
 38   
 39  __docformat__ = "restructuredtext en" 
 40   
 41   
 42  # redundant for dependency reduction - keep in sync with pysourceinfo.objectinfo 
 43  __MT_UNKNOWN = 0 
 44  __MT_SOURCE = 1  # PY_SOURCE #1 
 45  __MT_COMPILED = 2  # PY_COMPILED #2 
 46  __MT_EXTENSION = 3  # C_EXTENSION #3 
 47  __MT_DIRECTORY = 5  # PKG_DIRECTORY #5 
 48  __MT_BUILTIN = 6  # C_BUILTIN #6 
 49  __MT_FROZEN = 7  # PY_FROZEN #7 
 50  __MT_COMPILED_OPT1 = 10  # PY_COMPILED | <opt1> # 2 | 8 
 51  __MT_COMPILED_OPT2 = 18  # PY_COMPILED | <opt2> # 2 | 16 
 52  __MT_COMPILED_DEBUG = 34  # PY_COMPILED # 2 
 53   
 54   
 55  _scname = re.compile( 
 56      r'''.*[\\\\/]([^.]+)[.]([^-]+)-([0-9]{2})[.](opt-[012])*([.].+)$''') 
 57   
 58  # removes init file from path - foreseen for package path to OID processing 
 59  CUT_INIT_PY = re.compile(r'__init__.py') 
 60   
61 -def getfilepathname_type(fpath):
62 """Type for stored module as defined by *imp*. 63 Args: 64 fpath: 65 Module path. 66 67 Returns: 68 Returns the type of the file. :: 69 70 result := ( 71 C_BUILTIN 72 | C_EXTENSION 73 | PKG_DIRECTORY 74 | PY_COMPILED 75 | PY_FROZEN 76 | PY_SOURCE 77 ) 78 79 Raises: 80 pass-through 81 """ 82 83 if not PYV35Plus: # for now the more frequent call 84 if not os.path.exists(fpath): 85 return 86 ret = getmoduleinfo(fpath) 87 if ret and ret[3] in (PY_SOURCE, PY_COMPILED, C_EXTENSION, PKG_DIRECTORY, C_BUILTIN, PY_FROZEN): 88 return ret[3] 89 90 else: 91 if not os.path.exists(fpath): 92 return 93 94 # 95 # the redundnacy of type-postfixes requires now prefix-analysis by '_scname' 96 # 97 98 # In [1]: from importlib.machinery import SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES 99 # ...: , OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES, EXTENSION_SUFFIXES 100 # 101 # In [3]: SOURCE_SUFFIXES 102 # Out[3]: ['.py'] 103 # 104 # In [4]: DEBUG_BYTECODE_SUFFIXES 105 # Out[4]: ['.pyc'] 106 # 107 # In [5]: OPTIMIZED_BYTECODE_SUFFIXES 108 # Out[5]: ['.pyc'] 109 # 110 # In [6]: BYTECODE_SUFFIXES 111 # Out[6]: ['.pyc'] 112 # 113 # In [7]: EXTENSION_SUFFIXES 114 # Out[7]: ['.cpython-36m-x86_64-linux-gnu.so', '.abi3.so', '.so'] 115 116 x = _scname.findall(fpath) 117 if x: 118 x = x[0] # trust... 119 else: 120 x = os.path.splitext(fpath) 121 122 if x[-1] in SOURCE_SUFFIXES: 123 return __MT_SOURCE 124 elif x[-1] in EXTENSION_SUFFIXES: 125 return __MT_EXTENSION 126 elif x[-1] in BYTECODE_SUFFIXES and not x[-2]: 127 return __MT_COMPILED 128 elif x[-1] in OPTIMIZED_BYTECODE_SUFFIXES and x[-2]: 129 if x[-2] == 'opt-1': 130 return __MT_COMPILED_OPT1 131 if x[-2] == 'opt-2': 132 return __MT_COMPILED_OPT2 133 raise SourceInfoError( 134 "Unknown opt:" + str(x[-2]) + " " + str(fpath)) 135 elif x[-1] in DEBUG_BYTECODE_SUFFIXES: 136 return __MT_COMPILED_DEBUG 137 return __MT_UNKNOWN
138 139
140 -def get_oid_filepathname(oid, spath=None):
141 """File path name for OID. 142 """ 143 find_spec = util.find_spec(oid, spath) 144 if not find_spec is None: 145 return find_spec.origin
146 147
148 -def getpythonpath(pname, plist=None, **kw):
149 """Matches prefix from sys.path. 150 Foreseen to be used for canonical base references in unit tests. 151 This enables in particular for generic tests of file system positions 152 where originally absolute pathnames were required. 153 154 Args: 155 pname: 156 Path name to be searched an appropriate 157 pythonpath prefix for, either absolute 158 or relative to an item of *plist*. 159 160 plist: 161 List of possible python paths. 162 Alternative to *sys.path*. 163 164 default := sys.path 165 166 kw: 167 ispre: 168 If *True* adds a postfix path separator. 169 170 presolve: 171 Defines the path resolution 172 strategy:: 173 174 presolve := ( 175 P_FIRST 176 | P_LAST 177 | P_LONGEST 178 | P_SHORTEST 179 ) 180 181 The default is stored in:: 182 183 sourceinfo.presolve = P_FIRST 184 185 verify: 186 Verify against the filesystem, else no checks 187 are done. 188 189 default := True 190 191 Returns: 192 Returns the first matching path prefix from sys.path, 193 as 'normpath'. 194 Else:: 195 196 ispre == True: returns empty str - '' 197 else: returns None 198 199 Raises: 200 SourceInfoError 201 202 pass-through 203 204 """ 205 _pre = kw.get('ispre') 206 _verify = kw.get('verify', True) 207 if not pname: 208 # shortcut for empty/None - so following check know a parameter is provided 209 if _verify: 210 raise SourceInfoError('no valid param pname = "%s"' % (str(pname))) 211 if _pre: 212 return '' 213 return None 214 if not plist: 215 plist = sys.path 216 217 _presolve = kw.get('presolve', presolve) 218 219 # path or object name 220 try: 221 _fp = os.path.normpath(pname) 222 except TypeError: 223 try: 224 _fp = os.path.normpath(pname.__file__) 225 except: 226 if _verify: 227 raise SourceInfoError('not valid pname = %s' % (str(pname))) 228 229 # 230 # now shure, 'pname' is s.th. valid 231 # 232 233 if os.path.isabs(_fp): 234 if pname in plist: 235 # is a search path itself 236 237 if _verify and not os.path.exists(pname): 238 # absolute is has to exist itself 239 raise SourceInfoError('does not exist: \n abs: %s' % (str(pname))) 240 241 # simply not searched 242 if _pre: 243 return _fp + os.sep 244 return _fp 245 246 if _verify: 247 _fplst = [] 248 for x in plist: 249 if _fp.startswith(x) and os.path.exists(x): 250 _fplst.append(x) 251 252 else: 253 _fplst = [x for x in plist if _fp.startswith(x)] 254 255 else: 256 _fplst = [] 257 for _sp in plist: 258 if _verify and not os.path.exists(_sp + os.sep + _fp): 259 continue 260 261 if _pre: 262 _fplst.append(_sp + os.sep) 263 else: 264 _fplst.append(_sp) 265 266 if _verify and not _fplst: 267 raise SourceInfoError('does not exist: %s' % (str(pname))) 268 269 if _presolve == P_FIRST: 270 if _verify and not os.path.exists(_fplst[0]): 271 raise SourceInfoError('does not exist: %s' % (str(_fplst[0]))) 272 return _fplst[0] 273 274 elif _presolve == P_LAST: 275 if _verify and not os.path.exists(_fplst[-1]): 276 raise SourceInfoError('does not exist: %s' % (str(_fplst[-1]))) 277 return _fplst[-1] 278 279 elif _presolve == P_LONGEST: 280 _fpnew = '' 281 for x in _fplst: 282 if _verify and not os.path.exists(x): 283 continue 284 285 if len(x.split(os.sep)) > len(_fpnew.split(os.sep)): # number of path-items 286 _fpnew = x 287 288 elif _presolve == P_SHORTEST: 289 _fpnew = '' 290 for x in _fplst: 291 if _verify and not os.path.exists(x): 292 continue 293 294 if not _fpnew: 295 _fpnew = x 296 297 elif len(x.split(os.sep)) < len(_fpnew.split(os.sep)): # number of path-items 298 _fpnew = x 299 300 else: 301 raise SourceInfoError("unknown presolve = " + str(presolve)) 302 303 304 if _verify and not _fpnew: 305 raise SourceInfoError('does not exist: %s' % (str(pname))) 306 307 return _fpnew
308 309
310 -def getpythonpath_rel(pname, plist=None, **kw):
311 """Verifies an absolute or relative path name to be 312 reachable as a relative path to one item of the search 313 list *plist*. 314 315 Args: 316 pname: 317 Path name of a path searched for relative to 318 an item of *plist*. Absolute is cut, relative 319 is searched literally. 320 321 plist: 322 Search path alternative to *sys.path*. 323 324 *default := sys.path* 325 326 kargs: 327 presolve: 328 The type of path resolution: :: 329 330 pname is relative: 331 332 presolve := ( 333 P_FIRST 334 ) 335 336 pname is absolute: 337 338 presolve := ( 339 P_FIRST 340 | P_LAST 341 | P_LONGEST 342 | P_SHORTEST 343 ) 344 345 *default := pysourceinfo.presolve(P_FIRST)* 346 347 raw: 348 If *True* suppresses the call of 'os.path.normpath()':: 349 350 raw := ( 351 True # keep e.g. multiple and trailing *os.sep* 352 | False # apply os.path.normpath() 353 ) 354 default := False 355 356 normpath: 357 The method to be called for the normalization of the provided 358 input path. This is in particular required when cross-platform 359 path strings are provided. These are by default processed 360 internally with the *os.sep* of the current platform only. 361 362 The *API* is as defined by 'os.path.normpath':: 363 364 normapth := ( 365 <custom-call> 366 | <default-call> 367 ) 368 custom-call := ( 369 ntpath 370 | posixpath 371 | <any-compatible-api> 372 ) 373 default-call := os.path.normpath 374 375 verify: 376 Verify against the file system, else no checks 377 are done. 378 379 *default := True* 380 381 Returns: 382 First matched path from *sys.path* as normalized path. 383 Else:: 384 385 if PYTHONPATH[i] == pname: returns empty str - '' 386 else: 387 if verify: raise SourceInfoError 388 else: returns None 389 390 Raises: 391 SourceInfoError 392 393 pass-through 394 395 """ 396 _normpath = kw.get('normpath', os.path.normpath) 397 _raw = kw.get('raw', False) 398 _verify = kw.get('verify') 399 if not pname: 400 # shortcut for empty/None - so following check know a parameter is provided 401 if _verify: 402 raise SourceInfoError('no valid param pname = "%s"' % (str(pname))) 403 return pname 404 if not plist: 405 plist = sys.path 406 407 if os.path.isabs(pname): 408 _presolve = kw.get('presolve', presolve) 409 else: 410 _presolve = P_FIRST 411 412 413 # path or object name 414 if not _raw and _normpath: 415 try: 416 _fp = _normpath(pname) 417 except TypeError: 418 try: 419 _fp = _normpath(pname.__file__) 420 except: 421 if _verify: 422 raise SourceInfoError('not valid pname = %s' % (str(pname))) 423 else: 424 _fp = pname 425 426 427 # 428 # now shure, 'pname' is s.th. valid 429 # 430 if os.path.isabs(_fp): 431 if pname in plist: 432 # is a search path itself 433 434 if _verify and not os.path.exists(pname): 435 # absolute is has to exist itself 436 raise SourceInfoError('does not exist: \n abs: %s' % (str(pname))) 437 if pname == _fp: 438 return '' 439 return _fp 440 441 # for now assume x=='' is not valid 442 _fplst = [x for x in plist if x and _fp.startswith(x)] 443 444 else: 445 _fplst = [] 446 for _sp in plist: 447 if os.path.exists(_sp + os.sep + _fp): 448 _fplst.append(_sp) 449 else: 450 continue 451 452 # no plist 453 if not _fplst: 454 if pname == '': 455 if not _verify: 456 return '' 457 if not _verify: 458 return 459 raise SourceInfoError('does not exist: %s' % (str(pname))) 460 461 if _presolve == P_FIRST: 462 if _verify and not os.path.exists(_fplst[0]): 463 raise SourceInfoError('does not exist: %s' % (str(_fplst[0]))) 464 if os.path.isabs(_fp): 465 _ret = _fp[len(_fplst[0]):] 466 if os.path.isabs(_ret): 467 return _ret[1:] 468 else: 469 return _fp 470 471 elif _presolve == P_LAST: 472 if _verify and not os.path.exists(_fplst[-1]): 473 raise SourceInfoError('does not exist: %s' % (str(_fplst[-1]))) 474 if os.path.isabs(_fp): 475 _ret = _fp[len(_fplst[-1]):] 476 if os.path.isabs(_ret): 477 return _ret[1:] 478 else: 479 return _fp 480 481 elif _presolve == P_LONGEST: 482 _fpnew = '' 483 484 _shortest = '' 485 for x in _fplst: 486 if _verify and not os.path.exists(x): 487 continue 488 489 if not _shortest: 490 _shortest = x 491 492 for y in plist: 493 if x.startswith(y): 494 if y and len(y.split(os.sep)) < len(_shortest.split(os.sep)): 495 _shortest = y 496 497 if not _fpnew: 498 _fpnew = x 499 500 elif len(x.split(os.sep)) < len(_fpnew.split(os.sep)): 501 # number of path-items - search longest relative sub, neef shortest python path 502 _fpnew = x 503 504 505 if _fpnew: 506 if _fpnew[-1] == os.sep: 507 return pname[len(_fpnew):] 508 return pname[len(_fpnew) + 1:] 509 510 elif _presolve == P_SHORTEST: 511 # simple literal static sub directories only 512 kw['presolve'] = P_LONGEST 513 _longest = getpythonpath(_fp, plist, **kw) 514 _sp = _fp[len(_longest):] 515 516 if os.path.isabs(_sp): 517 return _sp[1:] 518 return _sp 519 520 else: 521 raise SourceInfoError("unknown presolve = " + str(presolve)) 522 523 if _verify and not _fpnew: 524 raise SourceInfoError('does not exist: %s' % (str(pname))) 525 526 return _fpnew
527 528
529 -def getpythonpath_rel_oid(pname, plist=None, **kw):
530 """Calls *getpythonpath_rel*, returns the relative 531 path as dotted *OID* relative to the search path. 532 533 Args: 534 For interface parameters refer to:: 535 536 sourceinfo.helper.getpythonpath_rel() 537 538 kargs: 539 For remaining parameters refer to 540 *getpythonpath_rel*. 541 542 restype: 543 Result type:: 544 545 restype = ( 546 OID_STR # OID as dottedt string representation 547 | OID_TUPLE # OID as tuple 548 | OID_LIST # OID as list 549 ) 550 551 default := OID_STR 552 553 sepin: 554 The path separator:: 555 556 default := os.sep 557 558 The function internally checks the presence of the provided path. 559 This is not cross-converted, thus requires native separator 560 characters. While the *NT* file system supports multiple separators, 561 the *posix* based file systems support regularly the slash '/' only. 562 Thus these fail when other separator characters are provided. For 563 advanced cross-capabilities refer to *filesysobjects*. 564 565 sepout: 566 The path separator:: 567 568 default := '.' 569 570 Returns: 571 Returns the provided result by *getpythonpath_rel* in accordance to 572 the requested result type - see *restype*. 573 574 Raises: 575 SourceInfoError 576 577 pass-through 578 579 """ 580 _restype = kw.get('restype', OID_STR) 581 _sepin = kw.get('sepin', os.sep) 582 _sepout = kw.get('sepout', '.') 583 584 if not isinstance(pname, ISSTR): 585 raise SourceInfoError("Supports string input only, got:" + str(pname)) 586 587 # __init__.py is not part of OID naming convention, so cut it off 588 _res = getpythonpath_rel(CUT_INIT_PY.sub('', pname), plist=None, **kw) 589 if not _res: 590 return 591 592 if isinstance(_res, ISSTR): 593 if _restype == OID_STR: 594 return re.sub(re.escape(_sepin), _sepout, _res) 595 elif _restype == OID_TUPLE: 596 return tuple(re.split(re.escape(_sepin), _res)) 597 elif _restype == OID_LIST: 598 return list(re.split(re.escape(_sepin), _res)) 599 600 raise SourceInfoError("Supports string conversion only, got:" + str(_res))
601 602
603 -def getstack_frame(spos=1, fromtop=False):
604 """List of current mem-addresses on stack frames. 605 606 Args: 607 spos: 608 Stack position. 609 610 fromtop: 611 Start on top, default from bottom. 612 613 Returns: 614 Returns the specified frame. 615 616 Raises: 617 passed through exceptions 618 619 """ 620 if fromtop: 621 return stack()[-1 * spos] 622 return stack()[spos]
623 624
625 -def getstack_len():
626 """Length of current stack. 627 628 Args: 629 none. 630 631 Returns: 632 Returns the length of current stack. 633 634 Raises: 635 pass-through 636 637 """ 638 return len(stack())
639
640 -def matchpath(path, pathlist=None, pmatch=P_FIRST):
641 """Match a file pathname of pathname on a pathlist. 642 643 Args: 644 path: 645 Path to be searched in *pathlist*. 646 647 pathlist: 648 Search path for a given *path*. 649 650 default := *sys.path* 651 652 pmatch: 653 Match criteria for search. :: 654 655 pmatch := ( 656 P_FIRST, P_LAST, P_SHORTEST, 657 P_LONGEST, P_IGNORE0 658 ) 659 660 default := *P_FIRST* 661 662 Returns: 663 Returns the matched pathname from pathlist. 664 665 Raises: 666 pass-through 667 668 """ 669 _buf = None 670 if pathlist == None: 671 pathlist = sys.path 672 673 if pmatch & P_IGNORE0: 674 _pl = pathlist[1:] 675 else: 676 _pl = pathlist 677 for sx in _pl: 678 p0 = os.path.normpath(sx) 679 if path.startswith(p0): 680 if pmatch & P_FIRST: 681 return p0 682 elif pmatch & P_LAST: 683 _buf = p0 684 elif pmatch & P_LONGEST: 685 if len(p0) > len(_buf): 686 _buf = p0 687 elif pmatch & P_SHORTEST: 688 if len(p0) < len(_buf) or not _buf: 689 _buf = p0 690 return _buf
691