Package setuplib :: Module setuplib

Source Code for Module setuplib.setuplib

  1  #-*- coding: utf-8 -*- 
  2  """Setuplib subcommand 'ENUMERATE'. 
  3  Enumerate available commands based on 'pkt_resources'. 
  4  Supports parameters and filters. 
  5  """ 
  6  from __future__ import absolute_import 
  7  from __future__ import print_function 
  8   
  9  import sys 
 10  import os 
 11  import re 
 12  import traceback 
 13   
 14  import distutils.cmd 
 15   
 16  import pkg_resources 
 17   
 18  import yapydata.datatree.datatree as datatree 
 19  import setuplib 
 20   
 21  __author__ = 'Arno-Can Uestuensoez' 
 22  __author_email__ = 'acue_sf2@sourceforge.net' 
 23  __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints" 
 24  __copyright__ = "Copyright (C) 2015-2019 Arno-Can Uestuensoez @Ingenieurbuero Arno-Can Uestuensoez" 
 25  __uuid__ = "239b0bf7-674a-4f53-a646-119f591af806" 
 26   
 27  __version__ = "01.01.005" 
 28   
 29  __product_family__ = "setuplib" 
 30  __product__ = "setuplib" 
 31  __product_component__ = "list_enty_points" 
 32   
 33   
34 -class SetuplibCommandsxError(setuplib.SetuplibError):
35 """Error on setuplib calls. 36 """ 37 pass
38 39
40 -class SetupListEntryPointsX(distutils.cmd.Command):
41 """List available entry points.""" 42 43 description = 'List available entry points.' 44 user_options = [ 45 ('debug', 'd', "Raises degree of debug traces of current context. Supports repetition. " 46 "Each raises the debug verbosity level of the context by one."), 47 ('exit', 'e', "Exit after command 'setuplib' immediately, ignore following. " 48 "Default := off."), 49 ('filter=', None, "Define a filter, for details refer to the manual. " 50 "Default: ''"), 51 ('format=', 'f', "Define display format. " 52 "See '--format=help. " 53 "Default: 'name,module_name,dist.egg_info:::fname'"), 54 ('group=', 'g', "Set group for scan, '--group=none' scans all, " 55 "'--group=console_scripts' displays all console scripts, " 56 "etc. " 57 "For current list: '--group=help', similar to '--list-groups'. " 58 "See 'PyPA.io'." 59 "Default: 'distutils.commands'"), 60 ('ignore-missing', 'i', "Ignore errors due to missing components, and continue. " 61 " For example in case of missing an optional. " 62 "Default: False. "), 63 ('layout=', None, "Define display layout. " 64 "See '--format=help'. " 65 "Default: table"), 66 ('list-groups', None, "Lists all scanned groups. " 67 "Is shortcut for the '--format' option, " 68 "refer to the manuals for additional details. " 69 "Default: names only." 70 ), 71 ('long', 'l', "List long format, similar to shell command 'ls -l'. " 72 "Default: off"), 73 ('quiet', 'q', "Suppress display including warnings. Display error messages only." 74 "Default: off"), 75 ('search-path', 'P', "Set the search path for requested resources. " 76 "Default: 'sys.path'"), 77 ('sort=', None, "Sort a specified field number. " 78 "Default: 0"), 79 ('verbose', 'v', "Raises verbosity of current context. Supports repetition. " 80 "Each raises the command verbosity level of the context by one. " 81 "The value is defined by the global option defined in " 82 "'Distribution'. " 83 "Refer to the manuals for the special behaviour when used as " 84 "either a global option(start 'verbose=1'), " 85 "or as a command context option(start 'verbose=0'). " 86 "Default:=1."), 87 ] 88 89
90 - def initialize_options(self):
91 """Define the API entrypoints and data of the command 'list'. 92 93 REMARK: verbose and debug are encapsulated/hidden by distutils. 94 95 """ 96 self.alias = None 97 self.at_once = None 98 self.debug = None 99 self.exit = None 100 self.filter = None 101 self.format = None 102 self.group = None 103 self.ignore_missing = None 104 self.layout = None 105 self.long = None 106 self.list_groups = None 107 self.quiet = None 108 self.search_path = None 109 self.sort = None 110 self.verbose = None
111
112 - def finalize_options(self):
113 """Initializae the API of 'lis'. 114 """ 115 116 # quick-and-dirty hack to resolve the inconsistency of 117 # global and local verbose values of distutils 118 try: 119 # The context option is actually not set by the framework, 120 # instead the global option is reset and intialized to 121 # the number of occurances and passes to the initialization 122 # of the memeber 'self.verbose'. 123 # Thus the poll fails, while the value is already set via the framework. 124 # See code distutils.dist.Distribution. 125 # Anyhow...keeping it as a reminder. 126 _v_opts = self.distribution.get_option_dict('setuplib')['verbose'][1] 127 if _v_opts: 128 self.verbose += 1 129 except: 130 # fallback to the slightly erroneous behavior when the interface 131 # of distutils changes 132 pass 133 134 # global and local verbose values of distutils 135 try: 136 # See verbose for description of the global option quiet. 137 # See code distutils.dist.Distribution. 138 _q_opts = self.distribution.get_option_dict('setuplib')['quiet'][1] 139 if _q_opts: 140 self.quiet += 1 141 except: 142 # fallback to the slightly erroneous behavior when the interface 143 # of distutils changes 144 if self.quiet == None: 145 self.quiet = 0 146 pass 147 148 149 # debug 150 if self.debug == None: 151 self.debug = 0 152 153 154 if self.ignore_missing == None: 155 self.ignore_missing = False 156 157 if self.exit == None: 158 self.exit = False 159 160 if self.long == None: 161 self.long = 0 162 163 if self.sort == None: 164 self.sort = 0 165 166 if self.list_groups != None: 167 self.list_groups = True 168 169 if self.group == None: 170 # allow pre-selection of a set of groups, e.g. regular expression 171 if not self.list_groups: 172 self.group = 'distutils.commands' 173 elif self.group.lower() == 'none': 174 self.group = None 175 176 177 if self.layout == None: 178 self.layout = 'table' # list, table, xml, json, yaml 179 elif self.layout == 'help': 180 print() 181 print("Current layouts are: list, table") 182 print("Soon available: csv, ini, json, .properties, xml, yaml") 183 print() 184 sys.exit(0) 185 186 if self.format == None: 187 self.format = 'name,module_name,dist.egg_info:::fname' 188 elif self.format.lower() == 'help': 189 print("format := (list | table | csl | xml | json | yaml)") 190 sys.exit(0) 191 192 # set the internal format representation for the request - or default 193 _format = [] 194 for _f in self.format.split(','): 195 _fx = _f.split(':') 196 if len(_fx) == 0: 197 continue 198 elif _fx[0] and len(_fx) == 1: 199 _format.append([_fx[0], 0, 'auto', '']) 200 elif len(_fx) < 5: 201 _rec = [_fx[0], 0, 'auto', '',] 202 203 if _fx[1]: 204 _rec[1] = int(_fx[1]) 205 206 if _fx[2]: 207 if _fx[2].lower() not in ( 208 'cl', 'cr', 'clip', 'auto', 209 ): 210 raise SetuplibCommandsxError( 211 "parameter error:%s - %s - in: %s" %( 212 str(_fx[2]), 213 str(_f), 214 str(self.format), 215 ) 216 ) 217 218 _rec[2] = _fx[2].lower() 219 220 if _fx[3]: 221 # optional special format, passed untouched 222 _rec[3] = _fx[3] 223 224 if _rec[1] or int(_rec[1]) == 0: 225 # force auto 226 _rec[2] = 'auto' 227 228 _format.append(_rec) 229 230 else: 231 raise SetuplibCommandsxError( 232 "parameter error: %s - in: %s" %( 233 str(_f), 234 str(self.format), 235 ) 236 ) 237 self.format = _format 238 239 # for now reminder only 240 if self.filter == None: 241 self.filter = None 242 243 # 244 # assemble parameter for the external class 245 # 246 self.task = { 247 "alias": self.alias, 248 "debug": self.debug, 249 "exit": self.exit, 250 "format": self.format, 251 "filter": self.filter, 252 "group": self.group, 253 "ignore_missing": self.ignore_missing, 254 "layout": self.layout, 255 "list_groups": self.list_groups, 256 "long": self.long, 257 "quiet": self.quiet, 258 "search_path": self.search_path, 259 "sort": self.sort, 260 "verbose": self.verbose, 261 } 262 263 264 if self.at_once: 265 _m = MyDistributionData(self.distribution, self.task) 266 _m.enumerate(self.task) 267 _m.print() 268 sys.exit(0)
269
270 - def run(self):
271 """Creates documents. 272 Calls the defined and activated wrapper scripts. 273 """ 274 _m = MyDistributionData(self.distribution, self.task) 275 _m.enumerate(self.task) 276 277 _m.print()
278 279 # if self.task['list_groups']: 280 # _m.print_groups() 281 # 282 # else: 283 # _m.print() 284 285 286
287 -class MyDistributionData(object):
288 289 #: supported types of simple filter combination logic 290 combinelogic = { 291 'and': 0, #: True if all match 292 'or': 1, #: True if any match 293 'nand': 2, #: True if not all matches 294 'nor': 3, #: True if none matches 295 'xor': 4, #: True if one only matches 296 } 297
298 - def __init__(self, distribution, task):
299 300 self.distribution = distribution 301 302 self.task = task 303 304 #: simple flat cache of all extension points 305 self.ep_cache = {} 306 307 #: flat cache of all commands 308 self.cmd_ep = [] 309 310 #: flat cache of standard commands only - from setuptools(eventually distutils too) 311 self.cmd_std = [] 312 313 #: flat cache of local commands only - from setup() 314 self.cmd_local = [] 315 316 #: flat cache of extra - non-standard - commands from setuptools 317 self.cmd_ext_setuptools = [] 318 319 #: flat cache of extra - non-standard - commands from distutils - eventually 320 self.cmd_ext_distutils = [] 321 322 #: flat cache of extra - non-standard - commands from third-party 323 self.cmd_ext_misc = [] 324 325 #: flat cache of extra commands from setuplib 326 self.cmd_setuplib = []
327
328 - def enumerate(self, task):
329 """Calls *pkg_resources* and caches the results. 330 The cached data is queried later for details required 331 by the extended list and display commands. 332 333 The caching happens here only, which comprises the flat 334 data cache of all entry points and the additional 335 categorized cache of selective sets for later selection 336 filters. 337 338 The centralized preparation of the data for later filtering 339 eases the data handling significantly by moderate use of 340 additional resources. 341 342 Args: 343 self: 344 The current instance of this class. 345 346 Returns: 347 Results in the member variable:: 348 349 self.ep_cache 350 351 The content is a *dict* containing the iterated 352 entry points with the *<ep>.name* as key. 353 354 Raises: 355 pass-through 356 357 """ 358 _task = task 359 if not _task: 360 _task = self._task 361 362 if ( 363 _task['group'] == None 364 or _task['group'] in ('help',) 365 ): 366 self.ep_cache = list(pkg_resources.iter_entry_points(None)) 367 self.ep_cache = [list(x.values())[0] for x in self.ep_cache] 368 369 # 370 # the shortcut for help display only 371 # 372 if _task['group'] in ('help',): 373 _groups = [] 374 for e in self.ep_cache: 375 _groups.extend(e.dist.get_entry_map().keys()) 376 377 print() 378 print("Current groups:") 379 print() 380 for g in sorted(set(_groups)): 381 print(" " + str(g)) 382 print() 383 384 sys.exit(0) 385 386 else: 387 self.ep_cache = list(pkg_resources.iter_entry_points(_task['group'])) 388 389 if _task['filter']: 390 _filters = _task['filter'].split(';') 391 _combine = 0 392 393 _query_filters = [] 394 for _fx in _filters: 395 if _fx.lower() in 'and': 396 _combine = 0 397 continue 398 elif _fx.lower() == 'or': 399 _combine = 1 400 continue 401 elif _fx.lower() == 'nand': 402 _combine = 2 403 continue 404 elif _fx.lower() == 'nor': 405 _combine = 3 406 continue 407 elif _fx.lower() == 'xor': 408 _combine = 4 409 continue 410 # elif _fx.lower() == 'not': 411 # _combine = 5 412 # continue 413 414 _record_filter = _fx.split(":") 415 if len(_record_filter) > 1: 416 # a filter requires fieldname and rule for the spanned column 417 _query_filters.append( 418 (_record_filter[0].split('.'), re.compile(_record_filter[1])) 419 ) 420 elif len(_record_filter) == 1: 421 # formal, even may not match at all... 422 _query_filters.append((_record_filter[0].split('.'), '')) 423 424 if _task['debug'] > 0: 425 print("DBG:input entry points: " + str(len(self.ep_cache))) 426 427 _all_filters = len(_query_filters) 428 for epi in reversed(range(len(self.ep_cache))): 429 _match_cnt = 0 430 for _fpath,_fexpr in _query_filters: 431 432 try: 433 _v = datatree.DataTree(self.ep_cache[epi])(*_fpath, pysyn=True) 434 if not _fexpr: 435 _match_cnt += 1 436 elif _fexpr.search(str(_v)): 437 _match_cnt += 1 438 439 except datatree.YapyDataDataTreeOidError: 440 if _task['ignore_missing']: 441 break 442 else: 443 # fall through to filter logic 444 pass 445 446 else: 447 if not ( 448 (_combine == 0 and _match_cnt == _all_filters) # and 449 or (_combine == 1 and _match_cnt > 0) # or 450 or (_combine == 2 and _match_cnt < _all_filters) # nand 451 or (_combine == 3 and _match_cnt == 0) # nor 452 or (_combine == 4 and _match_cnt == 1) # xor 453 ): 454 self.ep_cache.pop(epi) 455 456 if _task['debug'] > 0: 457 print("DBG:filtered entry points: " + str(len(self.ep_cache))) 458 459 self.ep_index = {} 460 idx = 0 461 for e in self.ep_cache: 462 self.ep_index[e.name] = idx 463 idx += 1 464 465 return
466
467 - def print_groups(self, outlist, index, task):
468 469 """Print '--list-groups' output based on pre-filtered data. 470 Thus the complete set of filters processed in enumerate 471 are applicable. 472 """ 473 _t = self.task 474 self.ep_cache = list(pkg_resources.iter_entry_points(self.task['group'])) 475 if self.task['group'] == None: 476 self.ep_cache = [list(x.values())[0] for x in self.ep_cache] 477 478 _groups = {} 479 for e in self.ep_cache: 480 _gmap = e.dist.get_entry_map() 481 for k,v in _gmap.items(): 482 if not _groups.get(k): 483 _groups[k] = v 484 else: 485 _groups[k].update(v) 486 487 pass 488 489 490 # 491 # now print the list 492 # 493 494 if _t['layout'] == 'list': 495 #self.print_list(_list, _index, _t) 496 self.print_groups_list(_groups, index, self.task) 497 elif _t['layout'] == 'xml': 498 raise NotImplementedError("XML is not yet available") 499 elif _t['layout'] == 'json': 500 raise NotImplementedError("JSON is not yet available") 501 elif _t['layout'] == 'yaml': 502 raise NotImplementedError("yaml is not yet available") 503 elif _t['layout'] == 'csv': 504 raise NotImplementedError("csv is not yet available") 505 elif _t['layout'] == 'table': 506 self.print_groups_list(_groups, index, self.task) 507 #self.print_table(_list, _index, _t) 508 else: 509 raise SetuplibCommandsxError( 510 "Unknown layout: " + str(_t['layout']) 511 )
512 513
514 - def print_groups_list(self, outlist, index, task):
515 """Print '--list-groups' output based on pre-filtered data. 516 Thus the complete set of filters are applicable. 517 """ 518 519 _adjust_level = self.task['long'] 520 521 522 print() 523 print("Current groups:") 524 print() 525 _maxsize = 0 526 527 if _adjust_level == 3: 528 for k,v in sorted(outlist.items()): 529 if self.task['long']: 530 for k1,v1 in sorted(v.items()): 531 if len(k1) > _maxsize: 532 _maxsize = len(k1) 533 534 for k,v in sorted(outlist.items()): 535 print(" " + str(k)) 536 if self.task['long']: 537 if _adjust_level == 2: 538 _maxsize = 0 539 for k1,v1 in sorted(v.items()): 540 if len(k1) > _maxsize: 541 _maxsize = len(k1) 542 543 for k1,v1 in sorted(v.items()): 544 # I do not want to estimate, whether the tuple v1.attr could contain more than 1... 545 # so simply trust the str(v1) here... 546 _v1 = re.sub(k1 + ' *= *', '', str(v1)) 547 if _maxsize > 0: 548 _format = " {0:"+ str(_maxsize) + "} = {1}" 549 else: 550 _format = " {0} = {1}" 551 print( _format.format( 552 str(k1), 553 str(_v1), 554 ) 555 ) 556 print() 557 558 print() 559 sys.exit(0)
560
561 - def print(self):
562 """Prints the requested data. 563 564 The printout is again processed in two levels. 565 - print: 566 567 Prepares the record data for the appropriate format. 568 569 Calls the inteface:: 570 571 self.print_<format>(outlist, index, task) 572 573 outlist := The list of resulting keys within the *self.ep_cache*. 574 575 index := The sprocessed/sorted list of *(<key>, #index)* mapping 576 of utlist to *self.ep_cache*. 577 578 task := The parameters of the current task. 579 580 For example:: 581 582 self.print_table(outlist, index, task) 583 584 - print_<format>: 585 Prints out the records of the selcted output format. 586 587 Uses object data from *self*. 588 589 """ 590 591 _t = self.task 592 593 print() 594 if _t['group'] == None: 595 print("entry points of resource group: None == all") 596 else: 597 print("entry points of resource group: " + str(_t['group'])) 598 599 if _t['filter']: 600 print("applied filter: " + str(_t['filter'])) 601 602 print() 603 604 if _t['sort'] == 0: 605 _index = self.ep_index 606 _list = sorted(_index) 607 608 elif _t['sort'] == 1: 609 _index = {} 610 idx = 0 611 _fname = {} 612 for e in self.ep_cache: 613 _fname[e.module_name] = (e.name, idx,) 614 idx += 1 615 for x in sorted(_fname.keys()): 616 _index[_fname[x][0]] = _fname[x][1] 617 _list = _index 618 619 elif _t['sort'] != 0: 620 # fieldnames 621 _n = _t['sort'] 622 _nx = _n.split('.') 623 624 _index = {} 625 idx = 0 626 _fname = {} 627 _grp = _t['group'] == None 628 629 for _e in self.ep_cache: 630 _val = datatree.DataTree(_e)(*_nx, pysyn=True) 631 try: 632 _item = _fname[_val] 633 _item.append((_e.name, idx,)) 634 except KeyError: 635 _fname[_val] = [(_e.name, idx,),] 636 637 idx += 1 638 639 for x in sorted(_fname.keys()): 640 for xi in _fname[x]: 641 _index[xi[0]] = xi[1] 642 _list = _index 643 644 645 # 646 # now print the list 647 # 648 649 if self.task['list_groups']: 650 self.print_groups(_list, _index, _t) 651 652 else: 653 if _t['layout'] == 'list': 654 self.print_list(_list, _index, _t) 655 elif _t['layout'] == 'xml': 656 raise NotImplementedError("XML is not yet available") 657 elif _t['layout'] == 'json': 658 raise NotImplementedError("JSON is not yet available") 659 elif _t['layout'] == 'yaml': 660 raise NotImplementedError("yaml is not yet available") 661 elif _t['layout'] == 'csv': 662 raise NotImplementedError("csv is not yet available") 663 elif _t['layout'] == 'table': 664 self.print_table(_list, _index, _t) 665 else: 666 raise SetuplibCommandsxError( 667 "Unknown layout: " + str(_t['layout']) 668 )
669
670 - def print_table(self, outlist, index, task):
671 """Print table. 672 """ 673 # REMINDER:for tests:_header = [['name', 0, 'keep'], ['module_name', 0, 'keep'], ['dist.key', 0, 'keep']] 674 675 # import prepared format string 676 _header = task['format'] 677 _layout = task['layout'] 678 679 # determine maximum column widths for each field 680 for _e in self.ep_cache: 681 for i in range(len(_header)): 682 if _header[i][2] not in ('auto', ): 683 continue 684 685 _nx = _header[i][0].split('.') 686 687 try: 688 _val = datatree.DataTree(_e)(*_nx, pysyn=True) 689 except datatree.YapyDataDataTreeOidError: 690 _val = '' 691 692 if ( 693 _header[i][3] and _header[i][3] == 'fname' 694 and not task['long'] 695 ): 696 _val = os.path.basename(_val) 697 elif ( 698 _header[i][3] and _header[i][3] == 'abs' 699 ): 700 _val = os.path.abspath(_val) 701 702 if len(str(_val)) >= _header[i][1]: # in case of equal add the space 703 _header[i][1] = len(str(_val)) + 1 704 elif len(_header[i][0]) >= _header[i][1]: # in case of equal add the space 705 _header[i][1] = len(_header[i][0]) + 1 706 707 _head = [x[0] for x in _header] 708 _format = '' 709 _tabsep = '' 710 _l = len(_header) 711 for i in range(_l): 712 if _header[i][1] == 0 or i == _l - 1: 713 _format += "{%d}"%(i) 714 else: 715 _format += "{%d:%d}"%(i, _header[i][1]) 716 717 _tabsep += '-' * _header[i][1] 718 719 print(_format.format(*_head)) 720 print(_tabsep) 721 722 _ign = task['ignore_missing'] 723 for x in outlist: 724 try: 725 _e = self.ep_cache[index[x]] 726 _values = [] 727 for _f in range(len(_head)): 728 _nx = _head[_f].split('.') 729 _len = _header[_f][1] - 1 730 _lx = _header[_f][2] 731 try: 732 _v = datatree.DataTree(_e)(*_nx, pysyn=True) 733 _lv = len(str(_v)) 734 735 if ( 736 _header[_f][3] and _header[_f][3] == 'fname' 737 and not task['long'] 738 ): 739 _v = os.path.basename(_v) 740 elif ( 741 _header[_f][3] and _header[_f][3] == 'abs' 742 ): 743 _v = os.path.abspath(_v) 744 745 746 if _lv > _len: 747 if _lx == 'cl': 748 _v = str(_v)[-_len:] 749 elif _lx == 'cr': 750 _v = str(_v)[:_len] 751 elif _lx == 'clip': 752 #TODO: 753 pass 754 elif _lx == 'auto': 755 pass 756 757 _values.append(_v) 758 759 except datatree.YapyDataDataTreeOidError as e: 760 if _ign: 761 # the assumingly most generic type 762 _values.append('') 763 else: 764 # einfo = sys.exc_info() 765 # traceback.print_tb(einfo[2]) 766 traceback.print_exc() 767 print("\n\nHINT: You have selected a non-present path, use:\n" 768 "\n '--ignore-missing' / '-i' to continue" 769 "\n '--filter' for the pre-selection of a valid set\n" 770 ) 771 sys.exit(1) 772 773 # for now it's enough 774 # Python2 775 # raise(datatree.YapyDataDataTreeOidError, datatree.YapyDataDataTreeOidError(e), sys.exc_info()[2]) 776 # Python3 777 # raise datatree.YapyDataDataTreeOidError from e 778 779 780 if _ign: 781 if ( _values and len(_values) > 1 ) or ''.join([str(x) for x in _values]): 782 print(_format.format(*[str(v) for v in _values])) 783 else: 784 continue 785 else: 786 print(_format.format(*[str(v) for v in _values])) 787 788 except AttributeError: # as e: 789 raise 790 except KeyError: # as e: 791 raise 792 793 print()
794
795 - def print_list(self, outlist, index, task):
796 """Print list. 797 """ 798 # import prepared format string 799 _header = task['format'] 800 _layout = task['layout'] 801 802 _head = [x[0] for x in _header] 803 _format = '' 804 _commonprefix = 0 805 for i in range(len(_header)): 806 if _commonprefix < len(str(_header[i][0])): 807 _commonprefix = len(str(_header[i][0])) + 1 808 809 prewidth = 5 810 811 _format = "{0} {1:%d}: {2}"%(_commonprefix) 812 _idxstr = ' ' * prewidth 813 _prestr = "\n{0:%d}" % (prewidth) 814 _idx = 0 815 816 for x in outlist: 817 try: 818 for _f in range(len(_head)): 819 if _f: 820 _pre = _idxstr 821 else: 822 _pre = _prestr.format(_idx) 823 _idx += 1 824 825 try: 826 _v = datatree.DataTree(self.ep_cache[index[x]])( 827 *_head[_f].split('.'), pysyn=True 828 ) 829 830 if ( 831 _header[_f][3] and _header[_f][3] == 'fname' 832 and not task['long'] 833 ): 834 _v = os.path.basename(_v) 835 elif ( 836 _header[_f][3] and _header[_f][3] == 'abs' 837 ): 838 _v = os.path.abspath(_v) 839 840 _x = _format.format( 841 _pre, 842 _head[_f], 843 _v, 844 ) 845 except datatree.YapyDataDataTreeOidError: 846 if task['ignore_missing']: 847 _x = _format.format( 848 _pre, 849 _head[_f], 850 '' 851 ) 852 else: 853 raise 854 855 print(_x) 856 857 except AttributeError: # as e: 858 raise 859 except KeyError: # as e: 860 raise 861 862 print()
863