Mercurial > hgrepos > Python > libs > ConfigMix
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 # |
