comparison configmix/__init__.py @ 144:7e6ec99d5ff5

Allow comments as keys and filter them by default
author Franz Glasner <hg@dom66.de>
date Fri, 13 Apr 2018 09:51:02 +0200
parents 647782859ae1
children e2e8d21b4122
comparison
equal deleted inserted replaced
143:252645c69c7b 144:7e6ec99d5ff5
18 __revision__ = "$Revision$" 18 __revision__ = "$Revision$"
19 19
20 20
21 import copy 21 import copy
22 22
23 from .compat import u
23 from .config import Configuration 24 from .config import Configuration
24 25
25 26
26 __all__ = ["load", "safe_load", 27 __all__ = ["load", "safe_load",
27 "set_loader", "default_loaders", 28 "set_loader", "default_loaders",
28 "Configuration"] 29 "Configuration"]
30
31
32 COMMENTS = [u("__comment"),
33 u("__doc"),
34 ]
35 """Prefixes for comment configuration keys that are to be handled as
36 comments
37
38 """
29 39
30 40
31 def load(*files): 41 def load(*files):
32 """Load the given configuration files, merge them in the given order 42 """Load the given configuration files, merge them in the given order
33 and return the resulting configuration dictionary. 43 and return the resulting configuration dictionary.
166 else: 176 else:
167 result[k] = deepcopy(v) 177 result[k] = deepcopy(v)
168 return result 178 return result
169 179
170 180
171 def merge(user, default): 181 def merge(user, default, filter_comments=True):
172 """Logically merge the configuration in `user` into `default`. 182 """Logically merge the configuration in `user` into `default`.
173 183
174 :param ~configmix.config.Configuration user: 184 :param ~configmix.config.Configuration user:
175 the new configuration that will be logically merged 185 the new configuration that will be logically merged
176 into `default` 186 into `default`
177 :param ~configmix.config.Configuration default: 187 :param ~configmix.config.Configuration default:
178 the base configuration where `user` is logically merged into 188 the base configuration where `user` is logically merged into
189 :param bool filter_comments: flag whether to filter comment keys that
190 start with any of the items in :data:`COMMENTS`
179 :returns: `user` with the necessary amendments from `default`. 191 :returns: `user` with the necessary amendments from `default`.
180 If `user` is ``None`` then `default` is returned. 192 If `user` is ``None`` then `default` is returned.
181 193
182 .. note:: The configuration in `default` is not changed but the 194 .. note:: The configuration in `user` is augmented/changed
183 configuration given in `user` is changed **inplace**. 195 **inplace**.
196
197 The configuration in `default` will be changed **inplace**
198 when filtering out comments (which is the default).
184 199
185 From http://stackoverflow.com/questions/823196/yaml-merge-in-python 200 From http://stackoverflow.com/questions/823196/yaml-merge-in-python
186 201
187 """ 202 """
188 if user is None: 203 if user is None:
204 if filter_comments:
205 _filter_comments(default)
189 return default 206 return default
207 if filter_comments:
208 _filter_comments(user)
190 if isinstance(user, dict) and isinstance(default, dict): 209 if isinstance(user, dict) and isinstance(default, dict):
191 for k, v in default.items(): 210 for k, v in default.items():
211 if filter_comments and _is_comment(k):
212 continue
192 if k not in user: 213 if k not in user:
193 user[k] = v 214 user[k] = v
194 else: 215 else:
195 user[k] = _merge(user[k], v) 216 user[k] = _merge(user[k], v, filter_comments)
196 return user 217 return user
197 218
198 219
199 def _merge(user, default): 220 def _merge(user, default, filter_comments):
200 """Recursion helper for :meth:`merge` 221 """Recursion helper for :meth:`merge`
201 222
202 """ 223 """
203 if isinstance(user, dict) and isinstance(default, dict): 224 if isinstance(user, dict) and isinstance(default, dict):
204 for k, v in default.items(): 225 for k, v in default.items():
226 if filter_comments and _is_comment(k):
227 continue
205 if k not in user: 228 if k not in user:
206 user[k] = v 229 user[k] = v
207 else: 230 else:
208 user[k] = _merge(user[k], v) 231 user[k] = _merge(user[k], v, filter_comments)
209 return user 232 return user
210 233
211 234
212 def safe_merge(user, default): 235 def safe_merge(user, default, filter_comments=True):
213 """A more safe version of :func:`merge` that makes deep copies of 236 """A more safe version of :func:`merge` that makes deep copies of
214 the returned container objects. 237 the returned container objects.
215 238
216 No given argument is ever changed inplace. Every object from `default` 239 Contrary to :func:`merge` no given argument is ever changed
217 is decoupled from the result -- so changing the `default` configuration 240 inplace. Every object from `default` is decoupled from the result
218 lates does not yield into a merged configuration later. 241 -- so changing the `default` configuration later does not yield
242 into a merged configuration later.
219 243
220 """ 244 """
221 if user is None: 245 if user is None:
246 if filter_comments:
247 _filter_comments(default)
222 return copy.deepcopy(default) 248 return copy.deepcopy(default)
223 user = copy.deepcopy(user) 249 user = copy.deepcopy(user)
250 if filter_comments:
251 _filter_comments(user)
224 if isinstance(user, dict) and isinstance(default, dict): 252 if isinstance(user, dict) and isinstance(default, dict):
225 for k, v in default.items(): 253 for k, v in default.items():
254 if filter_comments and _is_comment(k):
255 continue
226 if k not in user: 256 if k not in user:
227 user[k] = copy.deepcopy(v) 257 user[k] = copy.deepcopy(v)
228 else: 258 else:
229 user[k] = _safe_merge(user[k], v) 259 user[k] = _safe_merge(user[k], v, filter_comments)
230 return user 260 return user
231 261
232 262
233 def _safe_merge(user, default): 263 def _safe_merge(user, default, filter_comments):
234 """Recursion helper for :meth:`safe_merge` 264 """Recursion helper for :meth:`safe_merge`
235 265
236 """ 266 """
237 if isinstance(user, dict) and isinstance(default, dict): 267 if isinstance(user, dict) and isinstance(default, dict):
238 for k, v in default.items(): 268 for k, v in default.items():
269 if filter_comments and _is_comment(k):
270 continue
239 if k not in user: 271 if k not in user:
240 user[k] = copy.deepcopy(v) 272 user[k] = copy.deepcopy(v)
241 else: 273 else:
242 user[k] = _safe_merge(user[k], v) 274 user[k] = _safe_merge(user[k], v, filter_comments)
243 return user 275 return user
276
277
278 def _filter_comments(d):
279 """Recursively filter comments keys in the dict `d`.
280
281 Comment keys are keys that start with any of the items in
282 :data:`COMMENTS`.
283
284 """
285 if not isinstance(d, dict):
286 return
287 # use a copy of the keys because we change `d` while iterating
288 for k in list(d.keys()):
289 if _is_comment(k):
290 del d[k]
291 else:
292 if isinstance(d[k], dict):
293 _filter_comments(d[k])
294
295
296 def _is_comment(k):
297 for i in COMMENTS:
298 if k.startswith(i):
299 return True
300 return False
244 301
245 302
246 # 303 #
247 # Init loader defaults 304 # Init loader defaults
248 # 305 #