Mercurial > hgrepos > Python > libs > ConfigMix
comparison configmix/__init__.py @ 741:e069797f0e36
Implemented the new merge stragegies when merging lists: "extend" and "prepend"
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sun, 29 Oct 2023 17:15:41 +0100 |
| parents | 324ae9a56a75 |
| children | e4fad9cdd906 |
comparison
equal
deleted
inserted
replaced
| 740:cc9dff5fe0ca | 741:e069797f0e36 |
|---|---|
| 55 settings from commandline arguments. | 55 settings from commandline arguments. |
| 56 :type extras: dict-alike or None | 56 :type extras: dict-alike or None |
| 57 :keyword strict: enable strict parsing mode for parsers that support it | 57 :keyword strict: enable strict parsing mode for parsers that support it |
| 58 (e.g. to prevent duplicate keys) | 58 (e.g. to prevent duplicate keys) |
| 59 :type strict: bool | 59 :type strict: bool |
| 60 :keyword merge_lists: When ``None`` then lists will be overwritten | |
| 61 by the merge process. When ``extend`` then | |
| 62 lists will be extended instead. | |
| 63 This parameter is passed to :func:`.merge`. | |
| 64 :type merge_lists: str or None | |
| 60 :returns: the configuration | 65 :returns: the configuration |
| 61 :rtype: ~configmix.config.Configuration | 66 :rtype: ~configmix.config.Configuration |
| 62 | 67 |
| 63 """ | 68 """ |
| 64 defaults = kwargs.get("defaults") | 69 defaults = kwargs.get("defaults") |
| 65 extras = kwargs.get("extras") | 70 extras = kwargs.get("extras") |
| 66 strict = kwargs.get("strict", False) | 71 strict = kwargs.get("strict", False) |
| 72 merge_lists = kwargs.get("merge_lists", None) | |
| 67 if defaults is None: | 73 if defaults is None: |
| 68 ex = Configuration() | 74 ex = Configuration() |
| 69 else: | 75 else: |
| 70 ex = merge(None, Configuration(defaults)) | 76 ex = merge(None, Configuration(defaults), merge_lists=merge_lists) |
| 71 for f in files: | 77 for f in files: |
| 72 if f.startswith(constants.DIR_PREFIX): | 78 if f.startswith(constants.DIR_PREFIX): |
| 73 for f2 in _get_configuration_files_from_dir(f[5:]): | 79 for f2 in _get_configuration_files_from_dir(f[5:]): |
| 74 nx = _load_cfg_from_file(f2, ignore_unknown=True, strict=strict) | 80 nx = _load_cfg_from_file(f2, ignore_unknown=True, strict=strict) |
| 75 if nx is not None: | 81 if nx is not None: |
| 76 ex = merge(nx, ex) | 82 ex = merge(nx, ex, merge_lists=merge_lists) |
| 77 else: | 83 else: |
| 78 nx = _load_cfg_from_file(f, strict=strict) | 84 nx = _load_cfg_from_file(f, strict=strict) |
| 79 if nx is not None: | 85 if nx is not None: |
| 80 ex = merge(nx, ex) | 86 ex = merge(nx, ex, merge_lists=merge_lists) |
| 81 if extras: | 87 if extras: |
| 82 ex = merge(Configuration(extras), ex) | 88 ex = merge(Configuration(extras), ex, merge_lists=merge_lists) |
| 83 return Configuration(ex) | 89 return Configuration(ex) |
| 84 | 90 |
| 85 | 91 |
| 86 def safe_load(*files, **kwargs): | 92 def safe_load(*files, **kwargs): |
| 87 """Analogous to :func:`load` but do merging with :func:`safe_merge` | 93 """Analogous to :func:`load` but do merging with :func:`safe_merge` |
| 89 | 95 |
| 90 """ | 96 """ |
| 91 defaults = kwargs.get("defaults") | 97 defaults = kwargs.get("defaults") |
| 92 extras = kwargs.get("extras") | 98 extras = kwargs.get("extras") |
| 93 strict = kwargs.get("strict", False) | 99 strict = kwargs.get("strict", False) |
| 100 merge_lists = kwargs.get("merge_lists", None) | |
| 94 if defaults is None: | 101 if defaults is None: |
| 95 ex = Configuration() | 102 ex = Configuration() |
| 96 else: | 103 else: |
| 97 ex = safe_merge(None, Configuration(defaults)) | 104 ex = safe_merge(None, Configuration(defaults), merge_lists=merge_lists) |
| 98 for f in files: | 105 for f in files: |
| 99 if f.startswith(constants.DIR_PREFIX): | 106 if f.startswith(constants.DIR_PREFIX): |
| 100 for f2 in _get_configuration_files_from_dir(f[5:]): | 107 for f2 in _get_configuration_files_from_dir(f[5:]): |
| 101 nx = _load_cfg_from_file(f2, ignore_unknown=True, strict=strict) | 108 nx = _load_cfg_from_file(f2, ignore_unknown=True, strict=strict) |
| 102 if nx is not None: | 109 if nx is not None: |
| 103 ex = safe_merge(nx, ex) | 110 ex = safe_merge(nx, ex, merge_lists=merge_lists) |
| 104 else: | 111 else: |
| 105 nx = _load_cfg_from_file(f, strict=strict) | 112 nx = _load_cfg_from_file(f, strict=strict) |
| 106 if nx is not None: | 113 if nx is not None: |
| 107 ex = safe_merge(nx, ex) | 114 ex = safe_merge(nx, ex, merge_lists=merge_lists) |
| 108 if extras: | 115 if extras: |
| 109 ex = safe_merge(Configuration(extras), ex) | 116 ex = safe_merge(Configuration(extras), ex, merge_lists=merge_lists) |
| 110 return Configuration(ex) | 117 return Configuration(ex) |
| 111 | 118 |
| 112 | 119 |
| 113 def _get_configuration_files_from_dir(root): | 120 def _get_configuration_files_from_dir(root): |
| 114 """Returns the sorted list of files within directory `root` | 121 """Returns the sorted list of files within directory `root` |
| 387 else: | 394 else: |
| 388 result[k] = deepcopy(v) # noqa | 395 result[k] = deepcopy(v) # noqa |
| 389 return result | 396 return result |
| 390 | 397 |
| 391 | 398 |
| 392 def merge(user, default, filter_comments=True, extend_lists=False): | 399 def merge(user, default, filter_comments=True, merge_lists=None): |
| 393 """Logically merge the configuration in `user` into `default`. | 400 """Logically merge the configuration in `user` into `default`. |
| 394 | 401 |
| 395 :param ~configmix.config.Configuration user: | 402 :param ~configmix.config.Configuration user: |
| 396 the new configuration that will be logically merged | 403 the new configuration that will be logically merged |
| 397 into `default` | 404 into `default` |
| 398 :param ~configmix.config.Configuration default: | 405 :param ~configmix.config.Configuration default: |
| 399 the base configuration where `user` is logically merged into | 406 the base configuration where `user` is logically merged into |
| 400 :param bool filter_comments: flag whether to filter comment keys that | 407 :param bool filter_comments: flag whether to filter comment keys that |
| 401 start with any of the items in :data:`.COMMENTS` | 408 start with any of the items in :data:`.COMMENTS` |
| 402 :param bool extend_lists: When ``True`` then lists will be | 409 :param merge_lists: When ``None`` then lists will be overwritten |
| 403 extended instead of overwritten by the | 410 by the merge process. When ``extend`` then |
| 404 merge process | 411 lists will be extended instead. |
| 412 :type merge_lists: str or None | |
| 405 :returns: `user` with the necessary amendments from `default`. | 413 :returns: `user` with the necessary amendments from `default`. |
| 406 If `user` is ``None`` then `default` is returned. | 414 If `user` is ``None`` then `default` is returned. |
| 407 | 415 |
| 408 .. note:: The configuration in `user` is augmented/changed | 416 .. note:: The configuration in `user` is augmented/changed |
| 409 **inplace**. | 417 **inplace**. |
| 436 ukv = user[k] | 444 ukv = user[k] |
| 437 if ukv == constants.DEL_VALUE: | 445 if ukv == constants.DEL_VALUE: |
| 438 # do not copy | 446 # do not copy |
| 439 del user[k] | 447 del user[k] |
| 440 else: | 448 else: |
| 441 user[k] = _merge(ukv, v, filter_comments, extend_lists) | 449 user[k] = _merge(ukv, v, filter_comments, merge_lists) |
| 442 else: | 450 else: |
| 443 user[k] = v | 451 user[k] = v |
| 452 elif merge_lists is not None: | |
| 453 if merge_lists == "extend": | |
| 454 if isinstance(user, list) and isinstance(default, list): | |
| 455 for idx, value in enumerate(default): | |
| 456 user.insert(idx, value) | |
| 457 elif merge_lists == "prepend": | |
| 458 if isinstance(user, list) and isinstance(default, list): | |
| 459 user.extend(default) | |
| 460 else: | |
| 461 raise ValueError( | |
| 462 "unknown strategy for merge_lists: %s" % (merge_lists,)) | |
| 444 _filter_deletions(user) | 463 _filter_deletions(user) |
| 445 return user | 464 return user |
| 446 | 465 |
| 447 | 466 |
| 448 def _merge(user, default, filter_comments, extend_lists): | 467 def _merge(user, default, filter_comments, merge_lists): |
| 449 """Recursion helper for :func:`.merge` | 468 """Recursion helper for :func:`.merge` |
| 450 | 469 |
| 451 """ | 470 """ |
| 452 if isinstance(user, dict) and isinstance(default, dict): | 471 if isinstance(user, dict) and isinstance(default, dict): |
| 453 for k, v in default.items(): | 472 for k, v in default.items(): |
| 460 ukv = user[k] | 479 ukv = user[k] |
| 461 if ukv == constants.DEL_VALUE: | 480 if ukv == constants.DEL_VALUE: |
| 462 # do not copy | 481 # do not copy |
| 463 del user[k] | 482 del user[k] |
| 464 else: | 483 else: |
| 465 user[k] = _merge(ukv, v, filter_comments, extend_lists) | 484 user[k] = _merge(ukv, v, filter_comments, merge_lists) |
| 466 else: | 485 else: |
| 467 user[k] = v | 486 user[k] = v |
| 487 elif merge_lists is not None: | |
| 488 if merge_lists == "extend": | |
| 489 if isinstance(user, list) and isinstance(default, list): | |
| 490 for idx, value in enumerate(default): | |
| 491 user.insert(idx, value) | |
| 492 elif merge_lists == "prepend": | |
| 493 if isinstance(user, list) and isinstance(default, list): | |
| 494 user.extend(default) | |
| 495 else: | |
| 496 raise ValueError( | |
| 497 "unknown strategy for merge_lists: %s" % (merge_lists,)) | |
| 468 return user | 498 return user |
| 469 | 499 |
| 470 | 500 |
| 471 def safe_merge(user, default, filter_comments=True, extend_lists=False): | 501 def safe_merge(user, default, filter_comments=True, merge_lists=None): |
| 472 """A more safe version of :func:`.merge` that makes deep copies of | 502 """A more safe version of :func:`.merge` that makes deep copies of |
| 473 the returned container objects. | 503 the returned container objects. |
| 474 | 504 |
| 475 Contrary to :func:`.merge` no given argument is ever changed | 505 Contrary to :func:`.merge` no given argument is ever changed |
| 476 inplace. Every object from `default` is decoupled from the result | 506 inplace. Every object from `default` is decoupled from the result |
| 497 ukv = user[k] | 527 ukv = user[k] |
| 498 if ukv == constants.DEL_VALUE: | 528 if ukv == constants.DEL_VALUE: |
| 499 # do not copy | 529 # do not copy |
| 500 del user[k] | 530 del user[k] |
| 501 else: | 531 else: |
| 502 user[k] = _safe_merge(ukv, v, filter_comments, extend_lists) | 532 user[k] = _safe_merge(ukv, v, filter_comments, merge_lists) |
| 503 else: | 533 else: |
| 504 user[k] = copy.deepcopy(v) | 534 user[k] = copy.deepcopy(v) |
| 535 elif merge_lists is not None: | |
| 536 if merge_lists == "extend": | |
| 537 if isinstance(user, list) and isinstance(default, list): | |
| 538 for idx, value in enumerate(default): | |
| 539 user.insert(idx, copy.deepcopy(value)) | |
| 540 elif merge_lists == "prepend": | |
| 541 if isinstance(user, list) and isinstance(default, list): | |
| 542 user.extend(copy.deepcopy(default)) | |
| 543 else: | |
| 544 raise ValueError( | |
| 545 "unknown strategy for merge_lists: %s" % (merge_lists,)) | |
| 505 _filter_deletions(user) | 546 _filter_deletions(user) |
| 506 return user | 547 return user |
| 507 | 548 |
| 508 | 549 |
| 509 def _safe_merge(user, default, filter_comments, extend_lists): | 550 def _safe_merge(user, default, filter_comments, merge_lists): |
| 510 """Recursion helper for :func:`safe_merge` | 551 """Recursion helper for :func:`safe_merge` |
| 511 | 552 |
| 512 """ | 553 """ |
| 513 if isinstance(user, dict) and isinstance(default, dict): | 554 if isinstance(user, dict) and isinstance(default, dict): |
| 514 for k, v in default.items(): | 555 for k, v in default.items(): |
| 521 ukv = user[k] | 562 ukv = user[k] |
| 522 if ukv == constants.DEL_VALUE: | 563 if ukv == constants.DEL_VALUE: |
| 523 # do not copy | 564 # do not copy |
| 524 del user[k] | 565 del user[k] |
| 525 else: | 566 else: |
| 526 user[k] = _safe_merge(ukv, v, filter_comments, extend_lists) | 567 user[k] = _safe_merge(ukv, v, filter_comments, merge_lists) |
| 527 else: | 568 else: |
| 528 user[k] = copy.deepcopy(v) | 569 user[k] = copy.deepcopy(v) |
| 570 elif merge_lists is not None: | |
| 571 if merge_lists == "extend": | |
| 572 if isinstance(user, list) and isinstance(default, list): | |
| 573 for idx, value in enumerate(default): | |
| 574 user.insert(idx, copy.deepcopy(value)) | |
| 575 elif merge_lists == "prepend": | |
| 576 if isinstance(user, list) and isinstance(default, list): | |
| 577 user.extend(copy.deepcopy(default)) | |
| 578 else: | |
| 579 raise ValueError( | |
| 580 "unknown strategy for merge_lists: %s" % (merge_lists,)) | |
| 529 return user | 581 return user |
| 530 | 582 |
| 531 | 583 |
| 532 def _filter_comments(d): | 584 def _filter_comments(d): |
| 533 """Recursively filter comments keys in the dict `d`. | 585 """Recursively filter comments keys in the dict `d`. |
