diff configmix/config.py @ 16:f85dc4677c01

Implemented the real configuration dictionary with attribute access or variable substitution
author Franz Glasner <hg@dom66.de>
date Thu, 10 Mar 2016 09:39:35 +0100
parents
children 94b5e94fae44
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configmix/config.py	Thu Mar 10 09:39:35 2016 +0100
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+r"""The unified configuration dictionary with attribute support
+or variable substitution.
+
+"""
+
+from __future__ import division, absolute_import, print_function
+
+try:
+    from collections import OrderedDict as ConfigurationBase
+except ImportError:
+    try:
+        from ordereddict import OrderedDict as ConfigurationBase
+    except ImportError:
+        ConfigurationBase = dict
+
+from .variables import lookup_varns, lookup_filter
+from .compat import u
+
+
+__all__ = ["Configuration"]
+
+
+_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):
+
+    def getvar(self, varname, default=_MARKER):
+        """Get a variable of the form ``[[ns1.]ns2.]name`` - including
+        variables from other namespaces.
+
+        No variable expansion is done and no filters are applied.
+        """
+        varns, varname = self._split_ns(varname)
+        try:
+            if not varns:
+                lookupfn = self._lookupvar
+            else:
+                lookupfn = lookup_varns(varns)
+            varvalue = lookupfn(varname)
+        except KeyError:
+            if default is _MARKER:
+                raise KeyError("Variable %r not found" % varname)
+            else:
+                return default
+        else:
+            return varvalue
+
+    def getvar_s(self, varname, default=_MARKER):
+        """Get a variable - incliding variables from other namespaces.
+
+        Variables will be substituted recursively in the result.
+        """
+        try:
+            obj = self.getvar(varname)
+            return self.substitute_variables_in_obj(obj)
+        except KeyError:
+            if default is _MARKER:
+                raise
+            else:
+                return default
+
+    def _split_ns(self, s):
+        nameparts = s.split(':', 1)
+        if len(nameparts) == 1:
+            return (None, s, )
+        else:
+            return (nameparts[0], nameparts[1], )
+
+    def _split_filters(self, s):
+        nameparts = s.split('|')
+        if len(nameparts) == 1:
+            return (s, [], )
+        else:
+            return (nameparts[0].rstrip(), nameparts[1:], )
+
+    def _lookupvar(self, key, default=_MARKER):
+        """Lookup a variable.
+
+        If no default is given an unexisting `key` raises a `KeyError`
+        else `default` is returned.
+        """
+        parts = key.split('.')
+        try:
+            v = self[parts[0]]
+            for p in parts[1:]:
+                v = v[p]
+        except KeyError:
+            if default is _MARKER:
+                raise KeyError("Configuration variable %r not found" % key)
+            else:
+                return default
+        return v
+
+    # Speed
+    _TEXTTYPE = type(u(""))
+
+    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
+
+    # Speed
+    _STARTTOK = u(b"{{")
+    _ENDTOK = u(b"}}")
+
+    def expand_variable(self, s):
+        """Expand variables in a single string"""
+        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])
+            varvalue = self._apply_filters(filters, self.getvar_s(varname))
+            s = u(b"{0}{1}{2}").format(s[:start], varvalue, s[end+2:])
+            # don't re-evaluate because `self.getvar_s()` expands already
+            start = s.find(self._STARTTOK, start + len(varvalue))
+        return s
+
+    def _apply_filters(self, filters, value):
+        for name in filters:
+            try:
+                filterfn = lookup_filter(name)
+            except KeyError:
+                raise NameError("Filter %r not found" % name)
+            else:
+                value = filterfn(self, value)
+        return value