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: