Package yapydata :: Package datatree :: Module synini

Source Code for Module yapydata.datatree.synini

   1  # -*- coding: utf-8 -*- 
   2  """The *YapyData.ini* module provides the extended *INI* file syntax access  
   3  with optional extended syntax features. 
   4  The core syntax is defined by the standard library *configparser.ConfigParser*, 
   5  *YapyData.data* supports *Python2.7*, and *Python3.5+*. 
   6   
   7  """ 
   8   
   9  from __future__ import absolute_import 
  10   
  11  import os 
  12  import sys 
  13  import re 
  14   
  15  import itertools 
  16   
  17  from pythonids import PYV35Plus, ISSTR 
  18  from sourceinfo.fileinfo import getcaller_filename, getcaller_linenumber  
  19   
  20   
  21  if PYV35Plus: 
  22      import configparser  # @UnusedImport @UnresolvedImport 
  23  else: 
  24      # requires 'strict' parameter, thus the back-port, 
  25      # for now keep the construct 
  26      #import ConfigParser as configparser  # @Reimport @UnusedImport @UnresolvedImport 
  27   
  28      # decided to switch to the backport from Python3 - need __getitem__ 
  29      import configparser  # @UnusedImport # @Reimport 
  30   
  31   
  32  try: 
  33      from ConfigParser import ConfigParser  # @UnusedImport 
  34  except: 
  35      from configparser import ConfigParser  # @Reimport @UnusedImport 
  36   
  37   
  38  from collections import OrderedDict 
  39   
  40  import yapydata 
  41  from yapydata.datatree import YapyDataTreeError 
  42  from yapydata.datatree.datatree import DataTree, YapyDataDataTreeOidError 
  43   
  44   
  45  __author__ = 'Arno-Can Uestuensoez' 
  46  __license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints" 
  47  __copyright__ = "Copyright (C) 2019 Arno-Can Uestuensoez" \ 
  48                  " @Ingenieurbuero Arno-Can Uestuensoez" 
  49  __version__ = '0.1.1' 
  50  __uuid__ = "60cac28d-efe6-4a8d-802f-fa4fc94fa741" 
  51   
  52  __docformat__ = "restructuredtext en" 
53 54 55 -class YapyDataINIError(YapyDataTreeError):
56 """Generic INI syntax error. 57 """ 58 pass
59 60 61 #: fixed common keyword for special values - supported case sensitive and literally only 62 _KEYWORDS = {'true':True, 'false': False, 'null':None,} 63 64 65 # 66 # container types 67 # 68 _C_LIST = 1 #: list 69 _C_DICT = 2 #: dict 70 71 _C_BYTEARRAY = 4 72 _C_TUPLE = 8 73 _C_SET = 16 74 75 76 #: container markers - default generic base types 77 _CONTAINERS = { 78 'default': _C_LIST, # default 79 '': _C_LIST, # default too, will pop the empty item 80 'list': _C_LIST, # generic base type: list - array 81 'dict': _C_DICT, # generic base type: dict - object 82 } 83 84 85 #: Container markers for extended types specific to this module. 86 #: Do not use them in multi-syntax environments. 87 #: These are meant to be concatenated to _CONTAINERS by choice. 88 #: Some constraints apply to these types for Python. 89 #: E.g. immutable, non-subscriptable, so applicable in specific 90 #: data tree contexts only. 91 _CONTAINERSX = { 92 'bytearray': _C_BYTEARRAY, # array of bytes {0..256} 93 'tuple': _C_TUPLE, # immutable tuple 94 'set': _C_SET, # non-subscriptable set 95 } 96 97 98 #: Gets list entries. 99 #: win uses '\n' too: _CONCAT_LIST=re.compile(os.linesep + r'[ \t]*,') 100 _CONCAT_LIST=re.compile(r'\r{0,1}\n[ \t]*,') 101 102 #: Compiles regexpr for search paths spanning multiple lines. 103 _CONCAT_PATH=re.compile(r'\r{0,1}\n[ \t]*[:]') 104 105 #: Compiles regexpr for search paths spanning multiple lines. 106 _CLEAR_KEY=re.compile(r'^[ \t]*(.*?)[ \t]*$')
107 108 109 -def grow_branch(*subpath, **kargs):
110 """Creates a new branch including the assigned value to 111 the last node. The node types are defined by the types 112 of the *subpath* entries. 113 114 Supports a single linear branch only, no sub-branching. 115 116 The created path is validated for permitted types. 117 The derived types such as JSON have to support their 118 own branch method. Thus provided as a static method. 119 120 Args: 121 subpath: 122 Variable list/tuple of path keys and indexes. 123 124 kargs: 125 value: 126 Value to be assigned to the final node. 127 128 Returns: 129 A created branch. 130 131 Raises: 132 pass-through 133 134 """ 135 _val = kargs.get('value') 136 _subpath=list(subpath) 137 try: 138 ik = _subpath.pop(0) 139 except IndexError: 140 return _val 141 142 if isinstance(ik, int): 143 if ik != 0: 144 # no padding 145 raise YapyDataDataTreeOidError( 146 "new list requires idx==0: %s\n see: %s\n" %( 147 str(subpath), 148 str(ik) 149 ) 150 ) 151 return [grow_branch(*_subpath, value=_val)] 152 153 elif isinstance(ik, ISSTR) or ik in (True, False, None,): 154 # python only: (True, False, None,) 155 return {ik: grow_branch(*_subpath, value=_val)} 156 157 elif isinstance(ik, frozenset): 158 # python only 159 return {ik: grow_branch(*_subpath, value=_val)} 160 161 raise YapyDataDataTreeOidError( 162 "invalid subpath key/index: %s\n see: %s\n" %( 163 str(subpath), 164 str(ik) 165 ) 166 )
167
168 169 -def readout_data(xval, **kargs):
170 """Scan tree into JSON representation. Uses recursive calls to 171 readout_data through the logical tree spanned by *ConfigParser*. 172 173 Args: 174 xval: 175 The loaded input tree as received from *ConfigDataINIX*, 176 which is defined by *ConfigParser.read_file()*. 177 178 For example a node *cf*:: 179 180 datafile = os.path.abspath(fpname) 181 cf = ConfigDataINIX(datafile) 182 cf.load_parser() 183 cf.import_data(datafile) 184 185 is read out as :: 186 187 datatree = readout_data(cf) 188 189 190 Returns: 191 The resulting scanned data structure. 192 193 Raises: 194 pass-through 195 196 """ 197 return xval.readout_data()
198
199 200 -class MultiOrderedDict(OrderedDict):
201 """Add lists for multi-key items. 202 """
203 - def __setitem__(self, key, value):
204 """Intercept keys which are already present. 205 """ 206 if key in self: 207 if isinstance(value, list): 208 if isinstance(self[key], list): 209 try: 210 self[key].extend(value) 211 except KeyError: 212 self[key] = value 213 else: 214 self.__dict__[key] = value 215 return 216 # elif isinstance(value,str): 217 # return 218 super(MultiOrderedDict, self).__setitem__(key, value)
219
220 221 -class CaseSensitiveConfigParser(configparser.ConfigParser):
222 """Preserves case of all keys - including DEFAULT section. 223 Suppress formatting of *optionstr*, preserves upper case characters. 224 """
225 - def optionxform(self, optionstr):
226 return optionstr
227
228 229 -class DataTreeINI(DataTree):
230 """The the supported in-memory data tree representation of the *INI* syntax 231 supports a JSON compatible in memory data structure. The supported structure 232 relies on the standard *ConfigParser* - of *Python2* and *Python3* - with a 233 minimum set of custom addons. For a full set of extensions refer to the 234 package *multiconf*. 235 236 The basic scheme of the stored syntax is:: 237 238 ini-for-in-memory-json := { 239 <sections> 240 } 241 sections := <section> [, <sections>] 242 section := <section-attributes> 243 section-attributes := <attr-value-pairs> [, <section-attributes>] 244 attr-value-pairs := <attr-value-pair> [, <attr-value-pairs>] 245 attr-value-pair := <attr-name> <separator> <attr-value> 246 attr-name := "any valid ConfigParser string compatible to a JSON string" 247 attr-value := ( 248 <integer> | <integer-as-string> 249 | <float> | <float-as-string> 250 | <string> 251 | <list> 252 | <dict> 253 | <boolean> | <boolean-as-string> 254 | <null> | <null-as-string> 255 ) 256 257 integer-as-string := '"' <integer> '"' # treated as string 258 integer := "any non-quoted valid integer value" # treated as integer 259 float-as-string := '"' <float> '"' # treated as string 260 float := "any non-quoted valid integer value" # treated as float 261 string := "any valid string" 262 boolean := (true | false) 263 boolean-as-string := '"' <boolean> '"' # treated as string 264 null := null 265 null-as-string := '"' <null> '"' # treated as string 266 separator := ( ':' | '=' ) 267 268 The following derived proprietary container types are provided. These are 269 compatible with the basic representation of the higher layer *multiconf* 270 package, which also provides command line compilation and cross-conversion 271 utilies. 272 273 The *list* entries are represented as multiple lines of entries with leading 274 white-spaces and a comma. :: 275 276 list := <proprietary-basic-multiline-list-syntax> 277 proprietary-basic-multiline-list-syntax := <attr-name> <separator> <list-item> <CR> <list-items-multiline> <CR> 278 list-items-multiline := <ws> ',' <list-item> <CR> [<list-items-multiline>] 279 list-item := attr-value 280 ws := "arbitrary whitespace - <SPACE>, <tab>" 281 CR := "new line" 282 283 The *dict* entries are represented as multiple lines of entries with leading 284 white-spaces and a comma:: 285 286 dict := <proprietary-basic-multiline-dict-syntax> 287 proprietary-basic-multiline-dict-syntax := <attr-name> <separator> <attr-dict-item> <CR> <dict-items-multiline> <CR> 288 dict-items-multiline := <ws> ',' <dict-item> <CR> [<dict-items-multiline>] 289 dict-item := attr-value-pair 290 ws := "arbitrary whitespace - <SPACE>, <tab>" 291 CR := "new line" 292 293 The data tree is compatible with the standard packas *json*. 294 295 """ 296 297 @staticmethod
298 - def isvalid_top(value, **kargs):
299 """Validate compliance of top-node. 300 """ 301 if not isinstance(value, (dict, list, )): 302 raise YapyDataINIError( 303 "top 'node' must be a valid INI type for RFC-4627 processing: (dict, list), got: " 304 + str(type(value)) 305 ) 306 return
307
308 - def __init__(self, data={}, **kargs):
309 """ 310 Args: 311 data: 312 A JSON compliant in-memory data tree in accordance to RFC-7159:: 313 314 json-value := ( 315 object | array 316 | number 317 | string 318 | false | true 319 | null 320 ) 321 322 The equivalent *Python* types are - based on JSON-RFC7159 as canonical in-memory data:: 323 324 RFC-7159-type-for-json := ( 325 dict | list # see: object, array 326 | int | float # see: number 327 | str # see: unicode / for Python: ISSTR = (str(3) | unicode(2)) 328 | None | True | False # see: null, true, false 329 ) 330 331 The initial data defines the permitted type of the first item 332 within the *subpath* of the spanned data tree. 333 334 Thus atomic data types define a single node data tree only - new in RFC-7159. 335 336 kargs: 337 syntax: 338 Defines the syntax variant:: 339 340 syntax := ( 341 'base' # base core syntax for multi-syntax environments with global types 342 | 'ext' # extended syntax with specific extension for this module 343 | 'auto' # for now enables all choices - basically the same as 'ext' 344 ) 345 346 default := 'base' # maximum compatibility for multi-syntax environments 347 348 Returns: 349 None / initialized object 350 351 Raises: 352 YapyDataDataTreeError 353 354 pass-through 355 356 """ 357 358 _syn = kargs.get('syntax') 359 if _syn in ('ext', 'auto',): 360 # enable optional syntax extensions 361 global _CONTAINERS 362 _CONTAINERS.update(_CONTAINERSX) 363 364 # check data validity - pass eventual syntax parameters 365 DataTreeINI.isvalid_top(data, **kargs) 366 367 # actually initialize Python 368 # enabled syntax variant is not checked in depth - used currently by 'trust' ... and exceptions... 369 super(DataTreeINI, self).__init__(data)
370
371 - def __setattr__(self, name, value):
372 """Validates types of own data attributes. 373 374 Args: 375 name: 376 Name of the attribute. Following are reserved and 377 treated special: 378 379 * type: str - 'data' 380 The value is treated as the replacement of the internal 381 data attribute. Replaces or creates the complete data 382 of teh current instance. 383 384 value: 385 The value of the attribute. This by default superposes 386 present values by replacement. Non-present are created. 387 388 Returns: 389 390 Raises: 391 YapyDataINIError 392 393 """ 394 if name == 'data': 395 # 396 # replacement of current managed data 397 # 398 DataTreeINI.isvalid_top(value) 399 self.__dict__[name] = value 400 401 else: 402 # 403 # any standard attribute with standard behavior 404 # 405 return object.__setattr__(self, name, value)
406
407 - def import_data(self, fpname, key=None, node=None, **kargs):
408 """Reads an INI file. This is a simple basic method for the application 409 on the lower layers of the software stack. It is designed for minimal 410 dependencies. The used library is the standard *configparser* package. 411 412 Args: 413 fpname: 414 File path name of the *INI* file. :: 415 416 fpname := <ini-file-path-name> 417 ini-file-path-name := ( 418 <file-path-name> # with extension 419 | <file-path-name> '.ini' # without extension, for multiple syntaxes 420 ) 421 422 key: 423 The key for the insertion point:: 424 425 node[key] = <file-data> 426 427 default := None - replace self.data, 428 429 The caller is responsible for the containment of the provided 430 node within the data structure represented by this object. No 431 checks are performed. 432 433 node: 434 The node for the insertion of the read data.:: 435 436 default := <top> 437 438 Returns: 439 Reference to read data structure. 440 441 Raises: 442 YapyDataConfigError 443 444 pass-through 445 446 """ 447 if not os.path.isfile(fpname): 448 if not os.path.isfile(fpname + '.ini'): 449 raise YapyDataINIError("Missing file: " + str(fpname)) 450 else: 451 fpname = fpname + '.ini' 452 453 datafile = os.path.abspath(fpname) 454 cf = ConfigDataINIX(datafile) 455 cf.load_parser() 456 cf.import_data(datafile) 457 458 jval = cf.readout_data() 459 460 if key and node == None: 461 raise YapyDataINIError("Given key(%s) requires a valid node." % (str(key))) 462 463 if key: 464 node[key] = jval 465 else: 466 self.data = jval 467 468 return jval
469
470 471 -class ConfigDataINIX(object):
472 """A simple configuration file parser based on the standard *ConfigParser* class. 473 474 The final resulting data with completely applied syntax-extensions is available by 475 the method *readout_data()*. 476 477 Supports the data types:: 478 479 int, float, str 480 481 multi-line lists, 482 multi-line search-paths 483 484 null 485 true, false 486 487 The optional structure:: 488 489 global-attributes-without-section 490 491 The standard file extensions are:: 492 493 '.ini' 494 495 The supported stnadard features of *ConfigParser* include:: 496 497 interpolation 498 499 For detailed information refer to the parameters of the *__init__* method. 500 """ 501 502 suffixes = ('ini', ) #: standard suffixes for INI 503 504
505 - def __init__(self, conf, **kargs):
506 """ 507 Args: 508 **conf**: 509 The file path name of the configuration file. 510 511 kargs: 512 For additional parameters see *ConfigData*. 513 514 allow_no_value: 515 Enables the support for keys without values 516 of *INI* format files. Supported by the standard 517 library based *configparser* . The value 518 could be applied to derived custom parser:: 519 520 default := False 521 522 See parameter *allow_no_value* of 523 *configparser.ConfigParser*. 524 525 casesens: 526 Preserves case of keys:: 527 528 default := False 529 530 comment_prefixes: 531 Defines the comment prefixes:: 532 533 default := ('#', ';') 534 535 delimiters: 536 Defines the delimiters for *INI* format based 537 files. Supported by the standard library based 538 *configparser* . The value could be applied to 539 derived custom parser:: 540 541 default := ('=', ':',) 542 543 dict_type: 544 The type of dictionary to be used by the parent class:: 545 546 default := None 547 548 empty_lines_in_values:: 549 550 default := False 551 552 inline_comment_prefixes: 553 Defines the inline comment prefixes:: 554 555 default := None 556 557 interpolation: 558 Enables the creation and replacement of variables 559 within configuration files. Depends on the derived 560 class, when not supported simply ignored:: 561 562 default := True 563 564 See parameter *interpolation* of 565 *configparser.ConfigParser*. 566 567 sectionpaths: 568 Enables the conversion and interpretation of section names 569 as path entries. This is required, when the versatility 570 of the converted and/or merged syntaxes are of different degree, 571 and the actual used syntax data path elements could not be 572 converted. In this case an intermediate sub-syntax is supported 573 by a dotted path notation, which could be converted between the 574 applied syntaxes:: 575 576 sectionpaths := ( 577 True # long data paths e.g. from *JSON* are 578 # converted to dotted section names 579 # and vice versa 580 | False # requires compatible versatility, 581 # else raises exception 582 ) 583 584 default := True 585 586 587 sectionpaths=True: 588 The section names of the native *INIX* syntax by default 589 constitute a one-level-only depth of data structures for 590 the complexity of structure definition. 591 592 sectionpaths=False: 593 594 595 sectionpathsep: 596 Defines the character used in section names as path separator. 597 The character has to be valid as provided by the config parser of the syntax, 598 in particular a valid character for section names of *INI* files, and has 599 to be semantically suitable as a special separator character:: 600 601 sectionpathsep := [] 602 603 default := '.' 604 605 Valid only when *sectionpaths* is active. 606 607 strict: 608 Controls the verification of duplicate sections 609 when these are used:: 610 611 strict := ( 612 True # raise an exception for duplicates 613 | False # accept 614 ) 615 616 default := False 617 618 See *strict* option of *ConfigParser*. 619 620 quiet: 621 Suppresses display of non-errors:: 622 623 default := False 624 625 Returns: 626 None/object 627 628 Raises: 629 pass-through 630 631 """ 632 self.allow_no_value = kargs.get('allow_no_value', False) 633 self.casesens = kargs.get('casesens', False) 634 self.comment_prefixes = kargs.get('comment_prefixes', ('#', ';')) 635 self.delimiters = kargs.get('delimiters', ('=', ':',)) 636 self.dict_type = kargs.get('dict_type', None) 637 self.empty_lines_in_values = kargs.get('empty_lines_in_values', False) 638 self.inline_comment_prefixes = kargs.get('inline_comment_prefixes', None) 639 self.interpolation = kargs.get('interpolation', True) 640 self.quiet = kargs.get('quiet', False) 641 self.strict = kargs.get('strict', False) # changed default
642
643 - def __bool__(self):
644 try: 645 return len(self.confdata.sections()) > 0 646 except Exception: 647 return False
648
649 - def __nonzero__(self):
650 try: 651 return len(self.confdata.sections()) > 0 652 except Exception: 653 return False
654
655 - def __getitem__(self, key):
656 """Get the section object. 657 Requires for Python2 the backport from Python3 "configparser", see PyPI 658 """ 659 return self.confdata[key]
660
661 - def get(self, key):
662 """Get the section object - compatible to Python3 and 663 the backport of the *configparser*. 664 """ 665 if key in self.confdata.sections(): 666 return self.confdata[key] 667 return None
668
669 - def import_data(self, conf, **kargs):
670 """Reads the configuration file and imports data. Supported standard types are:: 671 672 'cfg', 'conf', 'ini', 673 674 The imported data for the suported standard syntax is interpreted as: 675 676 +----------------------------------------+-------------+-------------------+-------------+-------------------------+----------------------------------------------+ 677 | syntax of imported config | sections as | keys as | path as | insertion position | remark | 678 +========================================+=============+===================+=============+=========================+==============================================+ 679 | `conf <parser_conf.html>`_ | sections | keys to <section> | -- | top, <section>, DEFAULT | no rename and no nested sections, no globals | 680 +----------------------------------------+-------------+-------------------+-------------+-------------------------+----------------------------------------------+ 681 | `ini <parser_ini.html>`_ | sections | keys by section | -- | top, <section>, DEFAULT | no rename and no nested sections, no globals | 682 +----------------------------------------+-------------+-------------------+-------------+-------------------------+----------------------------------------------+ 683 | `inix <parser_inix.html>`_ | sections | keys by section | -- | top, <section>, DEFAULT | no rename and no nested sections, no globals | 684 +----------------------------------------+-------------+-------------------+-------------+-------------------------+----------------------------------------------+ 685 686 Args: 687 conf: 688 The file path name of the configuration file. 689 690 kargs: 691 Unknown options are passed through to the 692 configuration parser. 693 694 anchor: 695 The insertion point for the imported data:: 696 697 anchor := ( 698 <section> 699 | 'DEFAULT' 700 | <top> 701 ) 702 703 top := "imports valid INIX/INI/CONF files with sections only" 704 section := "the name of the section" 705 'DEFAULT' := "keyword defined by the standard library as 706 global defaults for interpolation" 707 708 default := <top> 709 710 strict: 711 Activates validation. The boolean value is 712 mapped to the corresponding option of the 713 called import parser: 714 715 +------------+----------+-------+--------+ 716 | parser | option | True | False | 717 +============+==========+=======+========+ 718 | conf | strict | True | False | 719 +------------+----------+-------+--------+ 720 | ini | strict | True | False | 721 +------------+----------+-------+--------+ 722 | inix | strict | True | False | 723 +------------+----------+-------+--------+ 724 725 syntax: 726 Force the provided syntax. For available 727 values refer to syntax multiplexer 728 *self.synmux*. 729 730 Returns: 731 True for success, else False or raises an exception. 732 733 Raises: 734 pass-through 735 736 """ 737 738 # sectionless 739 # add '[globaldummy]' for 'configparser' 740 # will be removed by 'read_out' 741 with open(conf) as fp: 742 self.confdata.read_file( 743 itertools.chain(['[globaldummy]'], fp), 744 source=os.path.dirname(conf) 745 ) 746 return self.confdata != []
747 748
749 - def keys(self):
750 return self.confdata.sections()
751
752 - def load_parser(self, **kargs):
753 """Loads the syntax parser *configparser.ConfigParser* and 754 patches the behaviour for common 'conf' and 'cfg' format. 755 Supports the most of the files in '/etc' of POSIX based 756 OS. 757 758 Args: 759 kargs: 760 For the description of the options refer to the 761 method header of *multiconf.data.load_parser()*. 762 Unknown are passed through to the loaded 763 configuration parser. 764 765 For the following parameters see __init__: 766 767 * **allow_no_value** 768 * **casesens** 769 * **comment_prefixes** 770 * **inline_comment_prefixes** 771 * **delimiters** 772 * **dict_type** 773 * **empty_lines_in_values** 774 * **interpolation** 775 * **strict** 776 * **quiet** 777 778 Returns: 779 True, or raises an exception. 780 781 Raises: 782 pass-through 783 784 """ 785 kw_optparser = {} 786 787 try: 788 kw_optparser['strict'] = kargs.pop('strict') 789 except: 790 # try: 791 # kw_optparser['strict'] = self.conf['strict'] 792 # except KeyError: 793 # kw_optparser['strict'] = self.strict 794 pass 795 796 # _ip = self.conf.get('interpolation') 797 # if _ip: 798 # if _ip.lower() in ('false', '0', 'off', 'no'): 799 # kw_optparser['interpolation'] = None 800 # elif _ip.lower() not in ('false', '0', 'off', 'no'): 801 # raise MultiConfError("invalid value: interpolation=" + str(_ip)) 802 # elif self.interpolation == False: 803 # kw_optparser['interpolation'] = None 804 805 # _d = self.conf.get('delimiters', None) 806 # if _d: 807 # kw_optparser['delimiters'] = tuple(_d) 808 # elif self.delimiters: 809 # kw_optparser['delimiters'] = self.delimiters 810 # 811 # 812 # _d = self.conf.get('comment_prefixes', None) 813 # if _d: 814 # kw_optparser['comment_prefixes'] = tuple(_d) 815 # 816 # _d = self.conf.get('inline_comment_prefixes', None) 817 # if _d: 818 # kw_optparser['inline_comment_prefixes'] = tuple(_d) 819 # 820 # _d = self.conf.get('empty_lines_in_values', None) 821 # if _d: 822 # kw_optparser['empty_lines_in_values'] = tuple(_d) 823 # 824 # _d = self.conf.get('allow_no_value', None) 825 # if _d: 826 # kw_optparser['allow_no_value'] = _d 827 # elif not kargs.get('allow_no_value'): 828 # kw_optparser['allow_no_value'] = self.allow_no_value 829 # 830 # dict_type = kargs.get('dict_type', self.dict_type) 831 # if _d: 832 # kw_optparser['dict_type'] = dict_type 833 # 834 # try: 835 # _quiet = kargs.pop('quiet') 836 # except: 837 # pass 838 839 # activate case senitivity 840 # _cs = self.conf.get('casesens', False) 841 # _cs = self.conf.get('casesens') 842 # if _cs == None: 843 # _cs = kargs.get('casesens', False) 844 # if _cs not in (True, False): 845 # if _cs is None: 846 # _cs = False 847 # elif _cs.lower() in ('false', '0', 'off', 'no'): 848 # _cs = False 849 # elif _cs.lower() not in ('true', '1', 'on', 'yes'): 850 # raise MultiConfError("invalid value: casesens=" + str(_cs)) 851 852 _cs = True 853 if _cs: 854 _parser = CaseSensitiveConfigParser 855 else: 856 _parser = configparser.ConfigParser 857 858 if yapydata._debug > 1: 859 sys.stderr.write( 860 "DBG:%s:%s:_parser = %s\n" %( 861 str(getcaller_filename()), 862 str(getcaller_linenumber()), 863 str(_parser) 864 ) 865 ) 866 sys.stderr.write( 867 "DBG:%s:%s:kw_optparser = %s\n" %( 868 str(getcaller_filename()), 869 str(getcaller_linenumber()), 870 str(kw_optparser) 871 ) 872 ) 873 874 # self.kw_optparser = kw_optparser 875 # if dict_type: 876 # self.confdata = _parser( 877 # dict_type=dict_type, 878 # **kw_optparser 879 # ) 880 # else: 881 # self.confdata = _parser( 882 # **kw_optparser 883 # ) 884 885 self.confdata = _parser( 886 **kw_optparser 887 ) 888 889 return True
890
891 - def readout_data(self, **kargs):
892 """Returns the configuration as JSON compatible format. 893 Removes the special dummy section header 'globaldummy', 894 see *import_data*. 895 896 Args: 897 kargs: 898 stronly: 899 Scans all as *str* only. :: 900 901 stronly := ( 902 True # int and float are 903 # converted to str 904 | False # numeric values are kept 905 ) 906 907 default := False 908 909 Returns: 910 In-memory JSON compatible data. 911 912 Raises: 913 pass-through 914 915 """ 916 if self.confdata == None: 917 return 918 elif not self.confdata: 919 return str(self.confdata) 920 921 _stronly = kargs.get('stronly', False) 922 923 res = {} 924 for s in self.confdata.sections(): 925 if s == 'globaldummy': 926 for k, v, in self.confdata.items(s): 927 if _stronly: 928 res[k] = v 929 continue 930 931 try: 932 # int 933 res[k] = int(v) 934 except (ValueError, TypeError): 935 try: 936 # float 937 res[k] = float(v) 938 except (ValueError, TypeError): 939 try: 940 # common conceptual keywords: true, false, null 941 res[k] = _KEYWORDS[v] 942 except KeyError: 943 # try: 944 # # is meta keyword only - maps the multi-line entry to a container type syntax 945 # # default set is (dict, list), with the default selection of list 946 # container = _CONTAINERS[v] 947 # except KeyError: 948 try: 949 _v = _CONCAT_LIST.split(v) 950 if len(_v) > 1: 951 952 # is meta keyword only - maps the multi-line entry to a container type syntax 953 # default is list 954 try: 955 container = _CONTAINERS[_v[0]] 956 _v.pop(0) 957 except KeyError: 958 container = _C_LIST #: default container type 959 960 if container == _C_DICT: 961 # process comma separated multi-line dictionaries 962 res[k] = {} 963 for _vi in _v: 964 _dk,_dv = re.split(r'[=:]', _vi, 1) 965 _dk = str(_CLEAR_KEY.sub(r'\1', _dk)) 966 try: 967 res[k][_dk] = int(_dv) 968 except (ValueError, TypeError): 969 try: 970 res[_dk] = float(_dv) 971 except (ValueError, TypeError): 972 res[_dk] = _dv 973 974 elif container == _C_BYTEARRAY: 975 # process comma separated multi-line bytearray 976 # it is native and mutable, so do not need intermediate list 977 res[k] = bytearray() 978 for _vi in _v: 979 try: 980 res[k].append(int(_vi)) 981 except ValueError: 982 # wrong range as well as non-int have ValueError 983 raise YapyDataTreeError( 984 "Bytearray(0..256) failed: [%s] = %s" %( 985 str(k), 986 str(_vi), 987 ) 988 ) 989 990 else: # elif container == _C_LIST: 991 # process comma separated multi-line lists and related 992 res[k] = [] 993 for _vi in _v: 994 try: 995 res[k].append(int(_vi)) 996 except (ValueError, TypeError): 997 try: 998 res[k].append(float(_vi)) 999 except (ValueError, TypeError): 1000 res[k].append(_vi) 1001 1002 # now cast to closely related types 1003 if container == _C_TUPLE: 1004 res[k] = tuple(res[k]) 1005 elif container == _C_SET: 1006 res[k] = set(res[k]) 1007 1008 else: 1009 res[k] = v 1010 1011 except TypeError: 1012 res[k] = v 1013 1014 else: 1015 res[s] = {} 1016 for k, v, in self.confdata.items(s): 1017 if _stronly: 1018 res[s][k] = v 1019 continue 1020 1021 try: 1022 # int 1023 res[s][k] = int(v) 1024 except (ValueError, TypeError): 1025 try: 1026 # float 1027 res[s][k] = float(v) 1028 except (ValueError, TypeError): 1029 try: 1030 # common conceptual keywords: true, false, null 1031 res[s][k] = _KEYWORDS[v] 1032 except KeyError: 1033 try: 1034 _v = _CONCAT_LIST.split(v) 1035 if len(_v) > 1: 1036 # is meta keyword only - maps the multi-line entry to a container type syntax 1037 # default is list 1038 try: 1039 container = _CONTAINERS[_v[0]] 1040 _v.pop(0) 1041 except KeyError: 1042 container = _C_LIST #: default container type 1043 1044 if container == _C_DICT: 1045 res[s][k] = {} 1046 else: # elif container == _C_LIST: 1047 res[s][k] = [] 1048 1049 if container == _C_DICT: 1050 # process comma separated multi-line dictionaries 1051 #res[k] = {} 1052 for _vi in _v: 1053 _dk,_dv = re.split(r'[=:]', _vi, 1) 1054 _dk = str(_CLEAR_KEY.sub(r'\1', _dk)) 1055 try: 1056 res[s][k][_dk] = int(_dv) 1057 except (ValueError, TypeError): 1058 try: 1059 res[s][k][_dk] = float(_dv) 1060 except (ValueError, TypeError): 1061 res[s][k][_dk] = _dv 1062 1063 elif container == _C_BYTEARRAY: 1064 # process comma separated multi-line bytearray 1065 # it is native and mutable, so do not need intermediate list 1066 res[s][k] = bytearray() 1067 for _vi in _v: 1068 try: 1069 res[s][k].append(int(_vi)) 1070 except ValueError: 1071 # wrong range as well as non-int have ValueError 1072 raise YapyDataTreeError( 1073 "Bytearray(0..256) failed: [%s] = %s" %( 1074 str(k), 1075 str(_vi), 1076 ) 1077 ) 1078 1079 else: # elif container == _C_LIST: 1080 # process comma separated multi-line lists and related 1081 res[s][k] = [] 1082 for _vi in _v: 1083 try: 1084 res[s][k].append(int(_vi)) 1085 except (ValueError, TypeError): 1086 try: 1087 res[s][k].append(float(_vi)) 1088 except (ValueError, TypeError): 1089 res[s][k].append(_vi) 1090 1091 # now cast to closely related types 1092 if container == _C_TUPLE: 1093 res[s][k] = tuple(res[s][k]) 1094 elif container == _C_SET: 1095 res[s][k] = set(res[s][k]) 1096 1097 else: 1098 res[s][k] = v 1099 1100 except TypeError: 1101 if v == None: 1102 res[s][k] = None 1103 else: 1104 raise 1105 1106 return res
1107
1108 - def set(self, section, option, value):
1109 return self.confdata.set(section, option, value)
1110