Repository: Qiskit/qiskit

Issue:
Edit user config file programatically
It would be great to be able to modify the user config file from Qiskit. Something like `user_config['default']['option'] = True`.


Discussion:
@1ucian0 I'd like to work on it.
Sure! Thanks! 

Resolving patch:
diff --git a/qiskit/user_config.py b/qiskit/user_config.py
--- a/qiskit/user_config.py
+++ b/qiskit/user_config.py
@@ -143,6 +143,70 @@ def read_config_file(self):
                 self.settings["num_processes"] = num_processes
 
 
+def set_config(key, value, section=None, file_path=None):
+    """Adds or modifies a user configuration
+
+    It will add configuration to the currently configured location
+    or the value of file argument.
+
+    Only valid user config can be set in 'default' section. Custom
+    user config can be added in any other sections.
+
+    Changes to the existing config file will not be reflected in
+    the current session since the config file is parsed at import time.
+
+    Args:
+        key (str): name of the config
+        value (obj): value of the config
+        section (str, optional): if not specified, adds it to the
+            `default` section of the config file.
+        file_path (str, optional): the file to which config is added.
+            If not specified, adds it to the default config file or
+            if set, the value of `QISKIT_SETTINGS` env variable.
+
+    Raises:
+        QiskitUserConfigError: if the config is invalid
+    """
+    filename = file_path or os.getenv("QISKIT_SETTINGS", DEFAULT_FILENAME)
+    section = "default" if section is None else section
+
+    if not isinstance(key, str):
+        raise exceptions.QiskitUserConfigError("Key must be string type")
+
+    valid_config = {
+        "circuit_drawer",
+        "circuit_mpl_style",
+        "circuit_mpl_style_path",
+        "transpile_optimization_level",
+        "parallel",
+        "num_processes",
+    }
+
+    if section in [None, "default"]:
+        if key not in valid_config:
+            raise exceptions.QiskitUserConfigError("{} is not a valid user config.".format(key))
+
+    config = configparser.ConfigParser()
+    config.read(filename)
+
+    if section not in config.sections():
+        config.add_section(section)
+
+    config.set(section, key, str(value))
+
+    try:
+        with open(filename, "w") as cfgfile:
+            config.write(cfgfile)
+    except OSError as ex:
+        raise exceptions.QiskitUserConfigError(
+            "Unable to load the config file {}. Error: '{}'".format(filename, str(ex))
+        )
+
+    # validates config
+    user_config = UserConfig(filename)
+    user_config.read_config_file()
+
+
 def get_config():
     """Read the config file from the default location or env var
 


Test patch:
diff --git a/test/python/test_user_config.py b/test/python/test_user_config.py
--- a/test/python/test_user_config.py
+++ b/test/python/test_user_config.py
@@ -13,8 +13,10 @@
 # pylint: disable=missing-docstring
 
 import os
+import configparser as cp
 from uuid import uuid4
 
+from unittest import mock
 from qiskit import exceptions
 from qiskit.test import QiskitTestCase
 from qiskit import user_config
@@ -135,14 +137,66 @@ def test_all_options_valid(self):
             file.flush()
             config = user_config.UserConfig(self.file_path)
             config.read_config_file()
-            self.assertEqual(
-                {
-                    "circuit_drawer": "latex",
-                    "circuit_mpl_style": "default",
-                    "circuit_mpl_style_path": ["~", "~/.qiskit"],
-                    "transpile_optimization_level": 3,
-                    "num_processes": 15,
-                    "parallel_enabled": False,
-                },
-                config.settings,
-            )
+
+        self.assertEqual(
+            {
+                "circuit_drawer": "latex",
+                "circuit_mpl_style": "default",
+                "circuit_mpl_style_path": ["~", "~/.qiskit"],
+                "transpile_optimization_level": 3,
+                "num_processes": 15,
+                "parallel_enabled": False,
+            },
+            config.settings,
+        )
+
+    def test_set_config_all_options_valid(self):
+        self.addCleanup(os.remove, self.file_path)
+
+        user_config.set_config("circuit_drawer", "latex", file_path=self.file_path)
+        user_config.set_config("circuit_mpl_style", "default", file_path=self.file_path)
+        user_config.set_config("circuit_mpl_style_path", "~:~/.qiskit", file_path=self.file_path)
+        user_config.set_config("transpile_optimization_level", "3", file_path=self.file_path)
+        user_config.set_config("parallel", "false", file_path=self.file_path)
+        user_config.set_config("num_processes", "15", file_path=self.file_path)
+
+        config_settings = None
+        with mock.patch.dict(os.environ, {"QISKIT_SETTINGS": self.file_path}, clear=True):
+            config_settings = user_config.get_config()
+
+        self.assertEqual(
+            {
+                "circuit_drawer": "latex",
+                "circuit_mpl_style": "default",
+                "circuit_mpl_style_path": ["~", "~/.qiskit"],
+                "transpile_optimization_level": 3,
+                "num_processes": 15,
+                "parallel_enabled": False,
+            },
+            config_settings,
+        )
+
+    def test_set_config_multiple_sections(self):
+        self.addCleanup(os.remove, self.file_path)
+
+        user_config.set_config("circuit_drawer", "latex", file_path=self.file_path)
+        user_config.set_config("circuit_mpl_style", "default", file_path=self.file_path)
+        user_config.set_config("transpile_optimization_level", "3", file_path=self.file_path)
+
+        user_config.set_config("circuit_drawer", "latex", section="test", file_path=self.file_path)
+        user_config.set_config("parallel", "false", section="test", file_path=self.file_path)
+        user_config.set_config("num_processes", "15", section="test", file_path=self.file_path)
+
+        config = cp.ConfigParser()
+        config.read(self.file_path)
+
+        self.assertEqual(config.sections(), ["default", "test"])
+
+        self.assertEqual(
+            {
+                "circuit_drawer": "latex",
+                "circuit_mpl_style": "default",
+                "transpile_optimization_level": "3",
+            },
+            dict(config.items("default")),
+        )