comparison configmix/__init__.py @ 276:af371f9c016d

Allow deletion of key-value pairs when merging is done. When encountering the "{{::DEL::}}" special value the corresponding key-value pair is deleted.
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 03 Oct 2020 17:11:41 +0200
parents e2fd8fea1a4c
children 57fca7448740
comparison
equal deleted inserted replaced
275:e2fd8fea1a4c 276:af371f9c016d
11 """ 11 """
12 12
13 from __future__ import division, print_function, absolute_import 13 from __future__ import division, print_function, absolute_import
14 14
15 15
16 __version__ = "0.10.1.dev1" 16 __version__ = "0.11.0.dev1"
17 17
18 __revision__ = "|VCSRevision|" 18 __revision__ = "|VCSRevision|"
19 __date__ = "|VCSJustDate|" 19 __date__ = "|VCSJustDate|"
20 20
21 __all__ = ["load", "safe_load", 21 __all__ = ["load", "safe_load",
45 """ 45 """
46 46
47 DIR_PREFIX = u("<dir>") 47 DIR_PREFIX = u("<dir>")
48 """Prefix for configuration values to read other configuration files from 48 """Prefix for configuration values to read other configuration files from
49 given directory 49 given directory
50
51 """
52
53 DEL_VALUE = u("{{::DEL::}}")
54 """Value for configuration items to signal that the corresponding
55 key-value is to be deleted when configurations are merged
50 56
51 """ 57 """
52 58
53 59
54 def load(*files, **kwargs): 60 def load(*files, **kwargs):
416 **inplace**. 422 **inplace**.
417 423
418 The configuration in `default` will be changed **inplace** 424 The configuration in `default` will be changed **inplace**
419 when filtering out comments (which is the default). 425 when filtering out comments (which is the default).
420 426
427 If a value in `user` is equal to :data:`.DEL_VALUE`
428 (``{{::DEL::}}``) the corresponding key will be deleted from the
429 merged output.
430
421 From http://stackoverflow.com/questions/823196/yaml-merge-in-python 431 From http://stackoverflow.com/questions/823196/yaml-merge-in-python
422 432
423 """ 433 """
424 if user is None: 434 if user is None:
425 if filter_comments: 435 if filter_comments:
426 _filter_comments(default) 436 _filter_comments(default)
437 _filter_deletions(default)
427 return default 438 return default
428 if filter_comments: 439 if filter_comments:
429 _filter_comments(user) 440 _filter_comments(user)
430 if isinstance(user, dict) and isinstance(default, dict): 441 if isinstance(user, dict) and isinstance(default, dict):
431 for k, v in default.items(): 442 for k, v in default.items():
432 if filter_comments and _is_comment(k): 443 if filter_comments and _is_comment(k):
433 continue 444 continue
434 if k not in user: 445 if k in user:
446 if user[k] == DEL_VALUE:
447 # do not copy
448 del user[k]
449 else:
450 user[k] = _merge(user[k], v, filter_comments)
451 else:
435 user[k] = v 452 user[k] = v
436 else: 453 _filter_deletions(user)
437 user[k] = _merge(user[k], v, filter_comments)
438 return user 454 return user
439 455
440 456
441 def _merge(user, default, filter_comments): 457 def _merge(user, default, filter_comments):
442 """Recursion helper for :func:`.merge` 458 """Recursion helper for :func:`.merge`
444 """ 460 """
445 if isinstance(user, dict) and isinstance(default, dict): 461 if isinstance(user, dict) and isinstance(default, dict):
446 for k, v in default.items(): 462 for k, v in default.items():
447 if filter_comments and _is_comment(k): 463 if filter_comments and _is_comment(k):
448 continue 464 continue
449 if k not in user: 465 if k in user:
466 if user[k] == DEL_VALUE:
467 # do not copy
468 del user[k]
469 else:
470 user[k] = _merge(user[k], v, filter_comments)
471 else:
450 user[k] = v 472 user[k] = v
451 else:
452 user[k] = _merge(user[k], v, filter_comments)
453 return user 473 return user
454 474
455 475
456 def safe_merge(user, default, filter_comments=True): 476 def safe_merge(user, default, filter_comments=True):
457 """A more safe version of :func:`.merge` that makes deep copies of 477 """A more safe version of :func:`.merge` that makes deep copies of
464 484
465 """ 485 """
466 if user is None: 486 if user is None:
467 if filter_comments: 487 if filter_comments:
468 _filter_comments(default) 488 _filter_comments(default)
489 _filter_deletions(default)
469 return copy.deepcopy(default) 490 return copy.deepcopy(default)
470 user = copy.deepcopy(user) 491 user = copy.deepcopy(user)
471 if filter_comments: 492 if filter_comments:
472 _filter_comments(user) 493 _filter_comments(user)
473 if isinstance(user, dict) and isinstance(default, dict): 494 if isinstance(user, dict) and isinstance(default, dict):
474 for k, v in default.items(): 495 for k, v in default.items():
475 if filter_comments and _is_comment(k): 496 if filter_comments and _is_comment(k):
476 continue 497 continue
477 if k not in user: 498 if k in user:
499 if user[k] == DEL_VALUE:
500 # do not copy
501 del user[k]
502 else:
503 user[k] = _safe_merge(user[k], v, filter_comments)
504 else:
478 user[k] = copy.deepcopy(v) 505 user[k] = copy.deepcopy(v)
479 else: 506 _filter_deletions(user)
480 user[k] = _safe_merge(user[k], v, filter_comments)
481 return user 507 return user
482 508
483 509
484 def _safe_merge(user, default, filter_comments): 510 def _safe_merge(user, default, filter_comments):
485 """Recursion helper for :func:`safe_merge` 511 """Recursion helper for :func:`safe_merge`
487 """ 513 """
488 if isinstance(user, dict) and isinstance(default, dict): 514 if isinstance(user, dict) and isinstance(default, dict):
489 for k, v in default.items(): 515 for k, v in default.items():
490 if filter_comments and _is_comment(k): 516 if filter_comments and _is_comment(k):
491 continue 517 continue
492 if k not in user: 518 if k in user:
519 if user[k] == DEL_VALUE:
520 # do not copy
521 del user[k]
522 else:
523 user[k] = _safe_merge(user[k], v, filter_comments)
524 else:
493 user[k] = copy.deepcopy(v) 525 user[k] = copy.deepcopy(v)
494 else:
495 user[k] = _safe_merge(user[k], v, filter_comments)
496 return user 526 return user
497 527
498 528
499 def _filter_comments(d): 529 def _filter_comments(d):
500 """Recursively filter comments keys in the dict `d`. 530 """Recursively filter comments keys in the dict `d`.
523 # non-string key 553 # non-string key
524 return False 554 return False
525 return False 555 return False
526 556
527 557
558 def _filter_deletions(d):
559 """Recursively filter deletions in the dict `d`.
560
561 Deletions have values that equal :data:`.DEL_VALUE`.
562
563 """
564 if not isinstance(d, dict):
565 return
566 # use a copy of the items because we change `d` while iterating
567 for k, v in list(d.items()):
568 if v == DEL_VALUE:
569 del d[k]
570 else:
571 if isinstance(d[k], dict):
572 _filter_deletions(d[k])
573
574
528 # 575 #
529 # Init loader defaults: mode->loader and extension->mode 576 # Init loader defaults: mode->loader and extension->mode
530 # 577 #
531 mode_loaders.update(DEFAULT_MODE_LOADERS) 578 mode_loaders.update(DEFAULT_MODE_LOADERS)
532 for _pattern, _mode in DEFAULT_ASSOC: 579 for _pattern, _mode in DEFAULT_ASSOC: