Mercurial > hgrepos > Python > libs > ConfigMix
view configmix/config.py @ 314:043a6412be3c
Implemented new access methods .getvarl() and .getvarl_s
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Wed, 05 May 2021 01:32:07 +0200 |
| parents | 035b42a224ed |
| children | 98490375d90c |
line wrap: on
line source
# -*- coding: utf-8 -*- # :- # :Copyright: (c) 2015-2021, Franz Glasner. All rights reserved. # :License: BSD-3-Clause. See LICENSE.txt for details. # :- """The unified configuration dictionary with attribute support or variable substitution. """ from __future__ import division, absolute_import, print_function __all__ = ["Configuration"] import warnings try: from collections import OrderedDict as ConfigurationBase except ImportError: try: from ordereddict import OrderedDict as ConfigurationBase except ImportError: ConfigurationBase = dict try: from urllib.parse import urlsplit except ImportError: from urlparse import urlsplit from .variables import lookup_varns, lookup_filter from .compat import u from .constants import REF_NAMESPACE _MARKER = object() class _AttributeDict(ConfigurationBase): def __getattr__(self, name): try: v = self[name] except KeyError: raise AttributeError("%s has no attribute %r" % (type(self), name)) else: # Wrap a dict into another dict with attribute access support if isinstance(v, dict): return _AttributeDict(v) else: return v class Configuration(_AttributeDict): """The configuration dictionary with attribute support or variable substitution. .. note:: When retriving by attribute names variables will *not* substituted. """ # Speed _TEXTTYPE = type(u("")) _STARTTOK = u(b"{{") _ENDTOK = u(b"}}") _HIER_SEPARATOR = u(b'.') _NS_SEPARATOR = u(b':') _FILTER_SEPARATOR = u(b'|') _STARTTOK_REF = _STARTTOK + REF_NAMESPACE + _NS_SEPARATOR _ENDTOK_REF = _ENDTOK _DOT = u(b'.') def getvarl(self, *names, default=_MARKER, namespace=None): """Get a variable where the hierarchy is given in `names` as sequence and the namespace is given in `namespace`. No variable interpolation is done and no filters are applied. """ try: if not namespace: lookupfn = self._lookupvar else: if namespace == REF_NAMESPACE: lookupfn = self._lookupref else: lookupfn = lookup_varns(namespace) varvalue = lookupfn(*names) except KeyError: if default is _MARKER: raise KeyError("Variable %r not found" % (names,)) else: return default else: return varvalue def getvar(self, varname, default=_MARKER): """Get a variable of the form ``[ns:][[key1.]key2.]name`` - including variables from other namespaces. No variable interpolation is done and no filters are applied. """ varns, varname = self._split_ns(varname) if not varns: varnameparts = varname.split(self._HIER_SEPARATOR) else: varnameparts = (varname,) return self.getvarl(*varnameparts, namespace=varns) def getvarl_s(self, *names, default=_MARKER, namespace=None): """Get a variable - including variables from other namespaces. `names` and `namespace` are interpreted as in :meth:`.getvarl`. But variables will be interpolated recursively within the variable values and filters are applied. For more details see chapter :ref:`variable-interpolation`. """ try: obj = self.getvarl(*names, namespace=namespace) return self.substitute_variables_in_obj(obj) except KeyError: if default is _MARKER: raise else: return default def getvar_s(self, varname, default=_MARKER): """Get a variable - including variables from other namespaces. `varname` is interpreted as in :meth:`.getvar`. But variables will be interpolated recursively within the variable values and filters are applied. For more details see chapter :ref:`variable-interpolation`. """ try: obj = self.getvar(varname) return self.substitute_variables_in_obj(obj) except KeyError: if default is _MARKER: raise else: return default def getintvarl_s(self, *names, default=_MARKER, namespace=None): """Get a (possibly substituted) variable and coerce text to a number. """ s = self.getvarl_s(*names, default=default, namespace=namespace) if isinstance(s, self._TEXTTYPE): return int(s, 0) else: return s def getintvar_s(self, varname, default=_MARKER): """Get a (possibly substituted) variable and coerce text to a number. """ s = self.getvar_s(varname, default=default) if isinstance(s, self._TEXTTYPE): return int(s, 0) else: return s def getboolvarl_s(self, *names, default=_MARKER, namespace=None): """Get a (possibly substituted) variable and convert text to a boolean """ s = self.getvarl_s(*names, default=default, namespace=namespace) if isinstance(s, self._TEXTTYPE): sl = s.strip().lower() if sl not in self._BOOL_CVT: raise ValueError("Not a boolean: %r" % s) return self._BOOL_CVT[sl] else: return s def getboolvar_s(self, varname, default=_MARKER): """Get a (possibly substituted) variable and convert text to a boolean """ s = self.getvar_s(varname, default=default) if isinstance(s, self._TEXTTYPE): sl = s.strip().lower() if sl not in self._BOOL_CVT: raise ValueError("Not a boolean: %r" % s) return self._BOOL_CVT[sl] else: return s # Conversion of booleans _BOOL_CVT = { u('1'): True, u('yes'): True, u('true'): True, u('on'): True, u('0'): False, u('no'): False, u('false'): False, u('off'): False } def getfloatvarl_s(self, *names, default=_MARKER, namespace=None): """Get a (possibly substituted) variable and convert text to a float """ s = self.getvarl_s(*names, default=default, namespace=namespace) if isinstance(s, self._TEXTTYPE): return float(s) else: return s def getfloatvar_s(self, varname, default=_MARKER): """Get a (possibly substituted) variable and convert text to a float """ s = self.getvar_s(varname, default) if isinstance(s, self._TEXTTYPE): return float(s) else: return s def _split_ns(self, s): nameparts = s.split(self._NS_SEPARATOR, 1) if len(nameparts) == 1: return (None, s, ) else: return (nameparts[0], nameparts[1], ) def _split_filters(self, s): nameparts = s.split(self._FILTER_SEPARATOR) if len(nameparts) == 1: return (s, [], ) else: return (nameparts[0].rstrip(), nameparts[1:], ) def _lookupvar(self, *names, default=_MARKER): """Lookup a variable within a hierarchy. If no default is given an unexisting `name` raises a `KeyError` else `default` is returned. """ try: v = self.expand_if_reference(self[names[0]]) for p in names[1:]: v = self.expand_if_reference(v[p]) except TypeError: raise KeyError( "Configuration variable %r not found" "(missing intermediate keys?)" % (names,)) except KeyError: if default is _MARKER: raise KeyError( "Configuration variable %r not found" % (names,)) else: return default return v def _lookupref(self, key, default=_MARKER): """ `key` must be a configuration reference URI without any (namespace) prefixes and suffixes """ return self.expand_ref_uri(key, default=default) def expand_if_reference(self, v, default=_MARKER): """Check whether `v` is a configuration reference and -- if true -- then expand it. `v` must match the pattern ``{{ref:<REFERENCE>}}`` All non-matching texttypes and all non-texttypes are returned unchanged. """ if not isinstance(v, self._TEXTTYPE): return v if not v.startswith(self._STARTTOK_REF) \ or not v.endswith(self._ENDTOK_REF): return v return self.expand_ref_uri( v[len(self._STARTTOK_REF):-len(self._ENDTOK_REF)], default=default) def expand_ref_uri(self, uri, default=_MARKER): pu = urlsplit(uri) if pu.scheme or pu.netloc or pu.path or pu.query: raise ValueError("only fragment-only URIs are supported") if not pu.fragment: return self if pu.fragment.startswith(self._DOT): raise ValueError("relative refs not supported") return self.getvar(pu.fragment, default=default) def substitute_variables_in_obj(self, obj): """Recursively expand variables in the object tree `obj`.""" if isinstance(obj, self._TEXTTYPE): # a string - really replace the value return self.expand_variable(obj) elif isinstance(obj, list): return [self.substitute_variables_in_obj(i) for i in obj] elif isinstance(obj, tuple): tmp = [self.substitute_variables_in_obj(i) for i in obj] return type(obj)(tmp) elif isinstance(obj, dict): newdict = type(obj)() for k in obj: newdict[k] = self.substitute_variables_in_obj(obj[k]) return newdict elif isinstance(obj, set): newset = type(obj)() for i in obj: newset.add(self.substitute_variables_in_obj(i)) else: return obj def expand_variable(self, s): """Expand variables in the single string `s`""" start = s.find(self._STARTTOK, 0) while start != -1: end = s.find(self._ENDTOK, start) if end < 0: return s varname, filters = self._split_filters(s[start+2:end]) try: varvalue = self._apply_filters(filters, self.getvar_s(varname)) except KeyError: warnings.warn("Cannot expand variable %r in string " "%r" % (varname, s, ), UserWarning, stacklevel=1) raise if varvalue is None: varvalue = u("") # # Dont apply and type conversions to str if the whole `s` is # just one expansion # if (start == 0) and (end + 2 == len(s)): return varvalue replaced = s[:start] + u(str(varvalue)) s = replaced + s[end+2:] # don't re-evaluate because `self.getvar_s()` expands already start = s.find(self._STARTTOK, len(replaced)) return s def _apply_filters(self, filters, value): for name in filters: try: filterfn = lookup_filter(name) except KeyError: # # Convert to NameError because we find a missing filters # a very serious error. # raise NameError("Filter %r not found" % name) else: value = filterfn(self, value) return value
