Repository: microsoft/Qcodes

Issue:
Config can only use certain files
It is possible to load a config from a specified file (returning the config in dictionary form) 

`cfg = Config()`
`cfg.load_config('some_path_here')`

and it is possible to update the config (which looks in the cwd, the env and the home directory)

`cfg.update()` 

but the combined version of these where you specify the location of the config file you want to use as current_config doesn't exist. Is there a strong reason for this?



Discussion:
I think this is sensible. Probably load_config should have been a private method all along. It seems somewhat problematic in that it updates the path to the config file in the class but not the actual config file.  

I suggest extending update_config to take an optional file path that would be loaded as the last config file. 

Resolving patch:
diff --git a/qcodes/config/config.py b/qcodes/config/config.py
--- a/qcodes/config/config.py
+++ b/qcodes/config/config.py
@@ -69,7 +69,7 @@ class Config():
                                                       schema_file_name)
 
     # home dir, os independent
-    home_file_name = expanduser("~/{}".format(config_file_name))
+    home_file_name = expanduser(os.path.join("~", config_file_name))
     schema_home_file_name = home_file_name.replace(config_file_name,
                                                    schema_file_name)
 
@@ -78,7 +78,7 @@ class Config():
     schema_env_file_name = env_file_name.replace(config_file_name,
                                                  schema_file_name)
     # current working dir
-    cwd_file_name = "{}/{}".format(Path.cwd(), config_file_name)
+    cwd_file_name = os.path.join(Path.cwd(), config_file_name)
     schema_cwd_file_name = cwd_file_name.replace(config_file_name,
                                                  schema_file_name)
 
@@ -91,9 +91,10 @@ class Config():
     _diff_config: Dict[str, dict] = {}
     _diff_schema: Dict[str, dict] = {}
 
-    def __init__(self):
+    def __init__(self, path=None):
+        self.config_file_path = path
         self.defaults, self.defaults_schema = self.load_default()
-        self.current_config = self.update_config()
+        self.update_config()
 
     def load_default(self):
         defaults = self.load_config(self.default_file_name)
@@ -101,9 +102,10 @@ def load_default(self):
         self.validate(defaults, defaults_schema)
         return defaults, defaults_schema
 
-    def update_config(self):
+    def update_config(self, path=None):
         """
-        Load defaults and validates.
+        Load defaults updates with config file in path
+        (or cwd, env or home if path not specified) and validates.
         A  configuration file must be called qcodesrc.json
         A schema file must be called schema.json
         Configuration files (and their schema) are loaded and updated from the
@@ -121,24 +123,35 @@ def update_config(self):
         """
         config = copy.deepcopy(self.defaults)
         self.current_schema = copy.deepcopy(self.defaults_schema)
-
-        if os.path.isfile(self.home_file_name):
-            home_config = self.load_config(self.home_file_name)
-            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(config, env_config)
+        self.config_file_path = path or self.config_file_path
+        if self.config_file_path is not None:
+            config_file = os.path.join(self.config_file_path, self.config_file_name)
+            schema_file = config_file.replace(self.config_file_name,
+                                              self.schema_file_name)
+            path_config = self.load_config(config_file)
+            config = update(config, path_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(config, cwd_config)
-            self.validate(config, self.current_schema,
-                          self.schema_cwd_file_name)
+                          schema_file)
+        else:
+            if os.path.isfile(self.home_file_name):
+                home_config = self.load_config(self.home_file_name)
+                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(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(config, cwd_config)
+                self.validate(config, self.current_schema,
+                              self.schema_cwd_file_name)
+
+        self.current_config = config
 
         return config
 


Test patch:
diff --git a/qcodes/tests/test_config.py b/qcodes/tests/test_config.py
--- a/qcodes/tests/test_config.py
+++ b/qcodes/tests/test_config.py
@@ -1,9 +1,14 @@
 import copy
 import jsonschema
+import os
+import json
 
 from functools import partial
 from unittest.mock import mock_open, patch, PropertyMock
 from unittest import TestCase
+import pytest
+import tempfile
+
 from qcodes.config import Config
 
 VALID_JSON = "{}"
@@ -133,6 +138,22 @@ def side_effect(map, name):
     return map[name]
 
 
+@pytest.fixture(scope="function")
+def path_to_config_file_on_disk():
+
+    contents = {"a": 13, "b": 22, "h": 22,
+                "user": {"foo":  "2"},
+                "c": 4, "bar": False, "z": 5}
+
+    with tempfile.TemporaryDirectory() as tmpdirname:
+        with open(os.path.join(tmpdirname, 'qcodesrc.json'), 'w') as f:
+            f.write(json.dumps(contents))
+        with open(os.path.join(tmpdirname, 'qcodesrc_schema.json'), 'w') as f:
+            f.write(json.dumps(SCHEMA))
+
+        yield tmpdirname
+
+
 class TestConfig(TestCase):
     def setUp(self):
         self.conf = Config()
@@ -220,3 +241,17 @@ def test_update_and_validate_user_config(self, config, schema):
         self.conf.add("foo", "bar", "string", "foo", "bar")
         self.assertEqual(self.conf.current_config, UPDATED_CONFIG)
         self.assertEqual(self.conf.current_schema, UPDATED_SCHEMA)
+
+
+def test_update_from_path(path_to_config_file_on_disk):
+    cfg = Config()
+
+    # check that the default is still the default
+    cfg.update_config()
+    assert cfg["core"]["db_debug"] == False
+
+    cfg.update_config(path=path_to_config_file_on_disk)
+    assert cfg['a'] == 13
+    assert cfg['user']['foo'] == '2'
+    expected_path = os.path.join(path_to_config_file_on_disk, 'qcodesrc.json')
+    assert cfg.current_config_path == expected_path