Repository: microsoft/Qcodes

Issue:
Config  breaks on upstream updates.
If a user had a custom config and we update upstream by adding a new field.
Their config value will remove the new field added upstream, thus breaking the import of qcodes!

The other way around is impossible to prevent though. If we remove a field upstream there's no way to delete it locally ,or even to raise a warning , but that's cool because maybe local uncommitted doe depends on it :D  At worst it's a bit of extra noise! 


Discussion:
thanks @eendebakpt  and @nataliejpg  and @MerlinSmiles for spotting my horrible 🐛  

Resolving patch:
diff --git a/qcodes/config/config.py b/qcodes/config/config.py
--- a/qcodes/config/config.py
+++ b/qcodes/config/config.py
@@ -1,3 +1,4 @@
+import collections
 import copy
 import json
 import logging
@@ -122,19 +123,19 @@ def update_config(self):
 
         if os.path.isfile(self.home_file_name):
             home_config = self.load_config(self.home_file_name)
-            config.update(home_config)
+            config = update(config, home_config)
             self.validate(config, self.current_schema,
                           self.schema_home_file_name)
 
         if os.path.isfile(self.env_file_name):
             env_config = self.load_config(self.env_file_name)
-            config.update(env_config)
+            config = update(config, env_config)
             self.validate(config, self.current_schema,
                           self.schema_env_file_name)
 
         if os.path.isfile(self.cwd_file_name):
             cwd_config = self.load_config(self.cwd_file_name)
-            config.update(cwd_config)
+            config = update(config, cwd_config)
             self.validate(config, self.current_schema,
                           self.schema_cwd_file_name)
 
@@ -161,9 +162,7 @@ def validate(self, json_config=None, schema=None, extra_schema_path=None):
                     # be overwritten
                     new_user = json.load(f)["properties"]["user"]
                     user = schema["properties"]['user']
-                    user["properties"].update(
-                            new_user["properties"]
-                            )
+                    user["properties"].update(new_user["properties"])
                 jsonschema.validate(json_config, schema)
             else:
                 logger.warning(EMPTY_USER_SCHEMA.format(extra_schema_path))
@@ -232,11 +231,13 @@ def add(self, key, value, value_type=None, description=None, default=None):
             # update schema!
             schema_entry = {key: {"type": value_type}}
             if description is not None:
-                schema_entry = {key: {
-                                    "type": value_type,
-                                    "default": default,
-                                    "description": description}
-                                }
+                schema_entry = {
+                    key: {
+                        "type": value_type,
+                        "default": default,
+                        "description": description
+                    }
+                }
             # the schema is nested we only update properties of the user object
             user = self.current_schema['properties']["user"]
             user["properties"].update(schema_entry)
@@ -336,12 +337,7 @@ def describe(self, name):
         # add cool description to docstring
         base_docstring = """{}.\nCurrent value: {}. Type: {}. Default: {}."""
 
-        doc = base_docstring.format(
-                description,
-                val,
-                _type,
-                default
-                )
+        doc = base_docstring.format(description, val, _type, default)
 
         return doc
 
@@ -357,15 +353,14 @@ def __getattr__(self, name):
     def __repr__(self):
         old = super().__repr__()
         base = """Current values: \n {} \n Current path: \n {} \n {}"""
-        return base.format(self.current_config,
-                           self.current_config_path,
-                           old)
+        return base.format(self.current_config, self.current_config_path, old)
 
 
 class DotDict(dict):
     """
     Wrapper dict that allows to get dotted attributes
     """
+
     def __init__(self, value=None):
         if value is None:
             pass
@@ -403,3 +398,13 @@ def __deepcopy__(self, memo):
     # dot acces baby
     __setattr__ = __setitem__
     __getattr__ = __getitem__
+
+
+def update(d, u):
+    for k, v in u.items():
+        if isinstance(v, collections.Mapping):
+            r = update(d.get(k, {}), v)
+            d[k] = r
+        else:
+            d[k] = u[k]
+    return d


Test patch: