Source code for zconfigparser


"""Extend ``ConfigParser`` to add some inheritance functionality."""

import configparser
import collections

DEFAULT_ZSEP = ' : '
REVERSED = ('.',)


[docs]class ZDictError(Exception): """Base class for `ZDict` Exceptions."""
[docs]class ZKeyError(ZDictError, KeyError): """Raised when no zkey is found.""" def __init__(self, key): msg = "No key %r found in shortnames and longnames." % (key,) super().__init__(msg)
[docs]class DuplicateZKeyError(ZDictError): """Raised when duplicate zkeys are found.""" def __init__(self, new, old): msg = "Duplicate zkeys: %r. %r already exists." % (new, old) super().__init__(msg)
[docs]class RecursiveZkeyError(ZDictError): """Raised when circular zkey structure is found. cf. '[aa : bb]', '[bb : cc]', '[cc : aa]' """ def __init__(self, key): msg = "Recurcive zkey detected: %r." % (key,) super().__init__(msg)
[docs]class Error(Exception): """Base class for `ZConfigParser` Exceptions."""
[docs]class NoZSectionError(Error, configparser.NoSectionError): """Raised when no zsection is found.""" def __init__(self, section): super().__init__('No zsection: %r' % (section,))
[docs]class NoZOptionError(Error, configparser.NoOptionError): """Raised when no option in a zsection is found.""" def __init__(self, option, section): configparser.Error.__init__(self, 'No zoption: %r in zsection: %r' % (option, section)) self.option = option self.section = section self.args = (option, section)
# not used # class DuplicateZSectionError(Error, configparser.DuplicateSectionError): # """Raised when duplicate zsections are found. # cf. `ConfigParser.DuplicateSectionError` provides # 'source' and 'lineno' information when reading from a file. # """
[docs]class ZDict(collections.OrderedDict): """A custom dictionary used in `ZConfigParser`. It creates and keeps internal zsection dependency dictionaries. (They are ordinary ``dict``). Without this, `ZConfigParser` has to search all sections all the time and is very slow. """ def __init__(self, *args, **kwargs): self.ZSEP = kwargs.pop('ZSEP', DEFAULT_ZSEP) self.zdata = dict() self._zparents = dict() # used for valification super().__init__(*args, **kwargs) def _zsplit(self, key): keys = key.split(self.ZSEP) if self.ZSEP in REVERSED: keys.reverse() return keys def _zkey(self, key): if key in self: return key if key in self.zdata: return self.zdata[key] raise ZKeyError(key) def _get_shortnames(self, key, collected=None): if collected is None: collected = [] longname = self._zkey(key) shortnames = self._zsplit(longname) shortname = shortnames[0] if shortname in collected: raise RecursiveZkeyError(shortname) collected.append(shortname) if len(shortnames) > 1: for key in shortnames[1:]: self._get_shortnames(key, collected) return collected def __setitem__(self, key, value): """When setting, the dictionary memorizes zsections structure. Keys must be longnames. Used when `ConfigParser` reads files, dicts, etc.. """ shortnames = self._zsplit(key) shortname = shortnames[0] if len(shortnames) > 1: old = self._zparents.get(shortname) if old and not old == shortnames: raise DuplicateZKeyError(shortnames, old) self.zdata[shortname] = key self._zparents[shortname] = shortnames super().__setitem__(key, value) def zget(self, key): all_shortnames = self._get_shortnames(key) longnames = [self._zkey(s) for s in all_shortnames] values = [self[lo] for lo in longnames] return values
[docs] def zkeys(self): """Collect all shortnames and longnames. Two dictionary keys are combined (sections and zsections), so key ordering of `OrderedDict` part is not preserved. """ # `KeysView` is subclass of `set`. keys = self.keys() | self.zdata.keys() return collections.abc.KeysView(keys)
def zcontains(self, key): return key in self.zkeys() def __repr__(self): return super().__repr__()
[docs]class ZDictGen(object): """A supplement class needed to create `ZSEP` pre-initialized `ZDict`. To adjust for `ConfigParser` intialization argument `dict_type`. """ def __init__(self, *args, **kwargs): self._ZSEP = kwargs.pop('ZSEP', DEFAULT_ZSEP) self._args = args self._kwargs = kwargs def __call__(self): return ZDict(*self._args, ZSEP=self._ZSEP, **self._kwargs)
[docs]class ZConfigParser(configparser.ConfigParser): """ConfigParser, plus some section inheritance function. E.g. section ``[aa : bb]`` becomes ``[aa]``, and inherits and overrides section [bb]. Default separator word is ' : ', exactly one space before and after ':'. """ def __init__(self, *args, **kwargs): if len(args) > 1 or 'dict_type' in kwargs: msg = ("you can not assign 'dict_type' in ZConfigParser") raise ValueError(msg) self.ZSEP = kwargs.pop('ZSEP', DEFAULT_ZSEP) zd = ZDictGen(ZSEP=self.ZSEP) super().__init__(*args, dict_type=zd, **kwargs)
[docs] def get(self, section, option, **kwargs): """Override `ConfigParser`'s method. `ZConfigParser` only wraps Exceptions in `get`. Other 'get' (`getint` etc.) might leak (raise) `ConfigParser`'s Exceptions. """ try: return super().get(section, option, **kwargs) except configparser.NoSectionError: raise NoZSectionError(section) except configparser.NoOptionError: raise NoZOptionError(option, section)
def _unify_values(self, section, vars): """Override `ConfigParser`'s method. ConfigParser's `.get()` calls this function. The code is mostly the same as the original, just inserting dictionaries list, instead of a dictionary (sectiondict). """ sectiondict = [{}] try: sectiondict = self._sections.zget(section) except ZKeyError: if section != self.default_section: raise NoZSectionError(section) vardict = {} if vars: for key, value in vars.items(): if value is not None: value = str(value) vardict[self.optionxform(key)] = value return collections.ChainMap(vardict, *sectiondict, self._defaults)
[docs] def zsections(self): """Return all section shortnames and longnames.""" return self._sections.zkeys()
[docs] def has_zsection(self, section): """Check section name (whether short or long).""" return self._sections.zcontains(section)
[docs] def has_zoption(self, section, option): """Check option name in a zsection (whether short or long).""" try: self.get(section, option) return True except (NoZSectionError, NoZOptionError, ZKeyError): return False