Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7163801
D1017.id3235.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
5 KB
Subscribers
None
D1017.id3235.diff
View Options
diff --git a/swh/core/config.py b/swh/core/config.py
--- a/swh/core/config.py
+++ b/swh/core/config.py
@@ -7,6 +7,8 @@
import logging
import os
import yaml
+from itertools import chain
+from copy import deepcopy
logger = logging.getLogger(__name__)
@@ -178,6 +180,77 @@
return full_config
+def merge_configs(base, other):
+ """Merge two config dictionaries
+
+ This does merge config dicts recursively, with the rules, for every value
+ of the dicts (with 'val' not being a dict):
+
+ - None + type -> type
+ - type + None -> None
+ - dict + dict -> dict (merged)
+ - val + dict -> TypeError
+ - dict + val -> TypeError
+ - val + val -> val (other)
+
+ so merging
+
+ {
+ 'key1': {
+ 'skey1': value1,
+ 'skey2': {'sskey1': value2},
+ },
+ 'key2': value3,
+ }
+
+ with
+
+ {
+ 'key1': {
+ 'skey1': value4,
+ 'skey2': {'sskey2': value5},
+ },
+ 'key3': value6,
+ }
+
+ will give:
+
+ {
+ 'key1': {
+ 'skey1': value4, # <-- note this
+ 'skey2': {
+ 'sskey1': value2,
+ 'sskey2': value5,
+ },
+ },
+ 'key2': value3,
+ 'key3': value6,
+ }
+
+ Note that no type checking is done for anything but dicts.
+ """
+ if not isinstance(base, dict) or not isinstance(other, dict):
+ raise TypeError(
+ 'Cannot merge a %s with a %s' % (type(base), type(other)))
+
+ output = {}
+ allkeys = set(chain(base.keys(), other.keys()))
+ for k in allkeys:
+ vb = base.get(k)
+ vo = other.get(k)
+
+ if isinstance(vo, dict):
+ output[k] = merge_configs(vb is not None and vb or {}, vo)
+ elif isinstance(vb, dict) and k in other and other[k] is not None:
+ output[k] = merge_configs(vb, vo is not None and vo or {})
+ elif k in other:
+ output[k] = deepcopy(vo)
+ else:
+ output[k] = deepcopy(vb)
+
+ return output
+
+
def swh_config_paths(base_filename):
"""Return the Software Heritage specific configuration paths for the given
filename."""
diff --git a/swh/core/tests/test_config.py b/swh/core/tests/test_config.py
--- a/swh/core/tests/test_config.py
+++ b/swh/core/tests/test_config.py
@@ -223,3 +223,90 @@
assert os.path.exists(conf['path1']), "path1 should still exist!"
assert os.path.exists(conf['path2']), "path2 should now exist."
+
+
+def test_merge_config():
+ cfg_a = {
+ 'a': 42,
+ 'b': [1, 2, 3],
+ 'c': None,
+ 'd': {'gheez': 27},
+ 'e': {
+ 'ea': 'Mr. Bungle',
+ 'eb': None,
+ 'ec': [11, 12, 13],
+ 'ed': {'eda': 'Secret Chief 3',
+ 'edb': 'Faith No More'},
+ 'ee': 451,
+ },
+ 'f': 'Janis',
+ }
+ cfg_b = {
+ 'a': 43,
+ 'b': [41, 42, 43],
+ 'c': 'Tom Waits',
+ 'd': None,
+ 'e': {
+ 'ea': 'Igorrr',
+ 'ec': [51, 52],
+ 'ed': {'edb': 'Sleepytime Gorilla Museum',
+ 'edc': 'Nils Peter Molvaer'},
+ },
+ 'g': 'Hüsker Dü',
+ }
+
+ # merge A, B
+ cfg_m = config.merge_configs(cfg_a, cfg_b)
+ assert cfg_m == {
+ 'a': 43, # b takes precedence
+ 'b': [41, 42, 43], # b takes precedence
+ 'c': 'Tom Waits', # b takes precedence
+ 'd': None, # b['d'] takes precedence (explicit None)
+ 'e': {
+ 'ea': 'Igorrr', # a takes precedence
+ 'eb': None, # only in a
+ 'ec': [51, 52], # b takes precedence
+ 'ed': {
+ 'eda': 'Secret Chief 3', # only in a
+ 'edb': 'Sleepytime Gorilla Museum', # b takes precedence
+ 'edc': 'Nils Peter Molvaer'}, # only defined in b
+ 'ee': 451,
+ },
+ 'f': 'Janis', # only defined in a
+ 'g': 'Hüsker Dü', # only defined in b
+ }
+
+ # merge B, A
+ cfg_m = config.merge_configs(cfg_b, cfg_a)
+ assert cfg_m == {
+ 'a': 42, # a takes precedence
+ 'b': [1, 2, 3], # a takes precedence
+ 'c': None, # a takes precedence
+ 'd': {'gheez': 27}, # a takes precedence
+ 'e': {
+ 'ea': 'Mr. Bungle', # a takes precedence
+ 'eb': None, # only defined in a
+ 'ec': [11, 12, 13], # a takes precedence
+ 'ed': {
+ 'eda': 'Secret Chief 3', # only in a
+ 'edb': 'Faith No More', # a takes precedence
+ 'edc': 'Nils Peter Molvaer'}, # only in b
+ 'ee': 451,
+ },
+ 'f': 'Janis', # only in a
+ 'g': 'Hüsker Dü', # only in b
+ }
+
+
+def test_merge_config_type_error():
+ for v in (1, 'str', None):
+ with pytest.raises(TypeError):
+ config.merge_configs(v, {})
+ with pytest.raises(TypeError):
+ config.merge_configs({}, v)
+
+ for v in (1, 'str'):
+ with pytest.raises(TypeError):
+ config.merge_configs({'a': v}, {'a': {}})
+ with pytest.raises(TypeError):
+ config.merge_configs({'a': {}}, {'a': v})
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Jan 30, 3:51 PM (1 h, 37 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3223413
Attached To
D1017: Add a simple merge_configs function in config.py
Event Timeline
Log In to Comment