Mercurial > hgrepos > Python > libs > ConfigMix
comparison configmix/config.py @ 305:f529ca46dd50
Implemented the "ref" namespace to get configuration tree references.
BUGS:
- Tests should be done more thoroughly and extensively
- Interaction of tree references and variable substitution should be
tested more properly
- Documentation is missing yet
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 26 Apr 2021 09:42:42 +0200 |
| parents | d8361dd70d2d |
| children | 801cd16223e4 |
comparison
equal
deleted
inserted
replaced
| 304:d8361dd70d2d | 305:f529ca46dd50 |
|---|---|
| 20 except ImportError: | 20 except ImportError: |
| 21 try: | 21 try: |
| 22 from ordereddict import OrderedDict as ConfigurationBase | 22 from ordereddict import OrderedDict as ConfigurationBase |
| 23 except ImportError: | 23 except ImportError: |
| 24 ConfigurationBase = dict | 24 ConfigurationBase = dict |
| 25 try: | |
| 26 from urllib.parse import urlsplit | |
| 27 except ImportError: | |
| 28 from urlparse import urlsplit | |
| 25 | 29 |
| 26 from .variables import lookup_varns, lookup_filter | 30 from .variables import lookup_varns, lookup_filter |
| 27 from .compat import u | 31 from .compat import u |
| 32 from .constants import REF_NAMESPACE | |
| 28 | 33 |
| 29 | 34 |
| 30 _MARKER = object() | 35 _MARKER = object() |
| 31 | 36 |
| 32 | 37 |
| 53 .. note:: When retriving by attribute names variables will *not* | 58 .. note:: When retriving by attribute names variables will *not* |
| 54 substituted. | 59 substituted. |
| 55 | 60 |
| 56 """ | 61 """ |
| 57 | 62 |
| 63 # Speed | |
| 64 _TEXTTYPE = type(u("")) | |
| 65 _STARTTOK = u(b"{{") | |
| 66 _ENDTOK = u(b"}}") | |
| 67 _NS_SEPARATOR = u(b':') | |
| 68 _FILTER_SEPARATOR = u(b'|') | |
| 69 _STARTTOK_REF = _STARTTOK + REF_NAMESPACE + _NS_SEPARATOR | |
| 70 _ENDTOK_REF = _ENDTOK | |
| 71 _DOT = u(b'.') | |
| 72 | |
| 58 def getvar(self, varname, default=_MARKER): | 73 def getvar(self, varname, default=_MARKER): |
| 59 """Get a variable of the form ``[ns:][[key1.]key2.]name`` - including | 74 """Get a variable of the form ``[ns:][[key1.]key2.]name`` - including |
| 60 variables from other namespaces. | 75 variables from other namespaces. |
| 61 | 76 |
| 62 No variable interpolation is done and no filters are applied. | 77 No variable interpolation is done and no filters are applied. |
| 65 varns, varname = self._split_ns(varname) | 80 varns, varname = self._split_ns(varname) |
| 66 try: | 81 try: |
| 67 if not varns: | 82 if not varns: |
| 68 lookupfn = self._lookupvar | 83 lookupfn = self._lookupvar |
| 69 else: | 84 else: |
| 70 lookupfn = lookup_varns(varns) | 85 if varns == REF_NAMESPACE: |
| 86 lookupfn = self._lookupref | |
| 87 else: | |
| 88 lookupfn = lookup_varns(varns) | |
| 71 varvalue = lookupfn(varname) | 89 varvalue = lookupfn(varname) |
| 72 except KeyError: | 90 except KeyError: |
| 73 if default is _MARKER: | 91 if default is _MARKER: |
| 74 raise KeyError("Variable %r not found" % varname) | 92 raise KeyError("Variable %r not found" % varname) |
| 75 else: | 93 else: |
| 137 return float(s) | 155 return float(s) |
| 138 else: | 156 else: |
| 139 return s | 157 return s |
| 140 | 158 |
| 141 def _split_ns(self, s): | 159 def _split_ns(self, s): |
| 142 nameparts = s.split(':', 1) | 160 nameparts = s.split(self._NS_SEPARATOR, 1) |
| 143 if len(nameparts) == 1: | 161 if len(nameparts) == 1: |
| 144 return (None, s, ) | 162 return (None, s, ) |
| 145 else: | 163 else: |
| 146 return (nameparts[0], nameparts[1], ) | 164 return (nameparts[0], nameparts[1], ) |
| 147 | 165 |
| 148 def _split_filters(self, s): | 166 def _split_filters(self, s): |
| 149 nameparts = s.split('|') | 167 nameparts = s.split(self._FILTER_SEPARATOR) |
| 150 if len(nameparts) == 1: | 168 if len(nameparts) == 1: |
| 151 return (s, [], ) | 169 return (s, [], ) |
| 152 else: | 170 else: |
| 153 return (nameparts[0].rstrip(), nameparts[1:], ) | 171 return (nameparts[0].rstrip(), nameparts[1:], ) |
| 154 | 172 |
| 158 If no default is given an unexisting `key` raises a `KeyError` | 176 If no default is given an unexisting `key` raises a `KeyError` |
| 159 else `default` is returned. | 177 else `default` is returned. |
| 160 """ | 178 """ |
| 161 parts = key.split('.') | 179 parts = key.split('.') |
| 162 try: | 180 try: |
| 163 v = self[parts[0]] | 181 v = self.expand_if_reference(self[parts[0]]) |
| 164 for p in parts[1:]: | 182 for p in parts[1:]: |
| 165 v = v[p] | 183 v = self.expand_if_reference(v[p]) |
| 166 except TypeError: | 184 except TypeError: |
| 167 raise KeyError( | 185 raise KeyError( |
| 168 "Configuration variable %r not found" | 186 "Configuration variable %r not found" |
| 169 "(missing intermediate keys?)" % key) | 187 "(missing intermediate keys?)" % key) |
| 170 except KeyError: | 188 except KeyError: |
| 172 raise KeyError("Configuration variable %r not found" % key) | 190 raise KeyError("Configuration variable %r not found" % key) |
| 173 else: | 191 else: |
| 174 return default | 192 return default |
| 175 return v | 193 return v |
| 176 | 194 |
| 177 # Speed | 195 def _lookupref(self, key, default=_MARKER): |
| 178 _TEXTTYPE = type(u("")) | 196 """ |
| 197 `key` must be a reference URI without any (namespace) prefixes | |
| 198 and suffixes | |
| 199 | |
| 200 """ | |
| 201 return self.expand_ref_uri(key, default=default) | |
| 202 | |
| 203 def expand_if_reference(self, v, default=_MARKER): | |
| 204 """Check whether `v` is a reference and -- if true -- then expand it. | |
| 205 | |
| 206 `v` must match the pattern ``{{{ref:<REFERENCE>}}}`` | |
| 207 | |
| 208 All non-matching texttypes and all non-texttypes are returned | |
| 209 unchanged. | |
| 210 | |
| 211 """ | |
| 212 if not isinstance(v, self._TEXTTYPE): | |
| 213 return v | |
| 214 if not v.startswith(self._STARTTOK_REF) \ | |
| 215 or not v.endswith(self._ENDTOK_REF): | |
| 216 return v | |
| 217 return self.expand_ref_uri( | |
| 218 v[len(self._STARTTOK_REF):-len(self._ENDTOK_REF)], | |
| 219 default=default) | |
| 220 | |
| 221 def expand_ref_uri(self, uri, default=_MARKER): | |
| 222 pu = urlsplit(uri) | |
| 223 if pu.scheme or pu.netloc or pu.path or pu.query: | |
| 224 raise ValueError("only fragment-only URIs are supported") | |
| 225 if not pu.fragment: | |
| 226 return self | |
| 227 if pu.fragment.startswith(self._DOT): | |
| 228 raise ValueError("relative refs not supported") | |
| 229 return self.getvar(pu.fragment, default=default) | |
| 179 | 230 |
| 180 def substitute_variables_in_obj(self, obj): | 231 def substitute_variables_in_obj(self, obj): |
| 181 """Recursively expand variables in the object tree `obj`.""" | 232 """Recursively expand variables in the object tree `obj`.""" |
| 182 if isinstance(obj, self._TEXTTYPE): | 233 if isinstance(obj, self._TEXTTYPE): |
| 183 # a string - really replace the value | 234 # a string - really replace the value |
| 196 newset = type(obj)() | 247 newset = type(obj)() |
| 197 for i in obj: | 248 for i in obj: |
| 198 newset.add(self.substitute_variables_in_obj(i)) | 249 newset.add(self.substitute_variables_in_obj(i)) |
| 199 else: | 250 else: |
| 200 return obj | 251 return obj |
| 201 | |
| 202 # Speed | |
| 203 _STARTTOK = u(b"{{") | |
| 204 _ENDTOK = u(b"}}") | |
| 205 | 252 |
| 206 def expand_variable(self, s): | 253 def expand_variable(self, s): |
| 207 """Expand variables in the single string `s`""" | 254 """Expand variables in the single string `s`""" |
| 208 start = s.find(self._STARTTOK, 0) | 255 start = s.find(self._STARTTOK, 0) |
| 209 while start != -1: | 256 while start != -1: |
