diff --git a/qiskit_ibm_runtime/accounts/management.py b/qiskit_ibm_runtime/accounts/management.py index 3eb91e657..c4275f1fe 100644 --- a/qiskit_ibm_runtime/accounts/management.py +++ b/qiskit_ibm_runtime/accounts/management.py @@ -13,15 +13,17 @@ """Account management related classes and functions.""" import os +import ast from typing import Optional, Dict from .exceptions import AccountNotFoundError from .account import Account, ChannelType from ..proxies import ProxyConfiguration -from .storage import save_config, read_config, delete_config +from .storage import save_config, read_config, delete_config, read_qiskitrc _DEFAULT_ACCOUNT_CONFIG_JSON_FILE = os.path.join( os.path.expanduser("~"), ".qiskit", "qiskit-ibm.json" ) +_QISKITRC_CONFIG_FILE = os.path.join(os.path.expanduser("~"), ".qiskit", "qiskitrc") _DEFAULT_ACCOUNT_NAME = "default" _DEFAULT_ACCOUNT_NAME_LEGACY = "default-legacy" _DEFAULT_ACCOUNT_NAME_CLOUD = "default-cloud" @@ -163,6 +165,33 @@ def get( if account_name in all_config: return Account.from_saved_format(all_config[account_name]) + if os.path.isfile(_QISKITRC_CONFIG_FILE): + qiskitrc_data = read_qiskitrc(_QISKITRC_CONFIG_FILE) + proxies = ( + ProxyConfiguration(ast.literal_eval(qiskitrc_data["proxies"])) + if "proxies" in qiskitrc_data + else None + ) + save_config( + filename=_DEFAULT_ACCOUNT_CONFIG_JSON_FILE, + name=_DEFAULT_ACCOUNT_NAME_IBM_QUANTUM, + overwrite=False, + config=Account( + token=qiskitrc_data.get("token", None), + url=qiskitrc_data.get("url", None), + instance=qiskitrc_data.get("default_provider", None), + verify=bool(qiskitrc_data.get("verify", None)), + proxies=proxies, + channel="ibm_quantum", + ) + .validate() + .to_saved_format(), + ) + default_config = read_config(filename=_DEFAULT_ACCOUNT_CONFIG_JSON_FILE) + return Account.from_saved_format( + default_config[_DEFAULT_ACCOUNT_NAME_IBM_QUANTUM] + ) + raise AccountNotFoundError("Unable to find account.") @classmethod diff --git a/qiskit_ibm_runtime/accounts/storage.py b/qiskit_ibm_runtime/accounts/storage.py index 7e6fcdcee..bd331c5d5 100644 --- a/qiskit_ibm_runtime/accounts/storage.py +++ b/qiskit_ibm_runtime/accounts/storage.py @@ -16,6 +16,7 @@ import logging import os from typing import Optional, Dict +from configparser import ConfigParser from .exceptions import AccountAlreadyExistsError logger = logging.getLogger(__name__) @@ -40,6 +41,16 @@ def save_config(filename: str, name: str, config: dict, overwrite: bool) -> None json.dump(data, json_out, sort_keys=True, indent=4) +def read_qiskitrc(qiskitrc_config_file: str) -> Dict[str, str]: + """Read credentials from a qiskitrc config and return as a dictionary.""" + config_parser = ConfigParser() + config_parser.read(qiskitrc_config_file) + account_data = {} + for name in config_parser.sections(): + account_data = dict(config_parser.items(name)) + return account_data + + def read_config( filename: str, name: Optional[str] = None, diff --git a/releasenotes/notes/load-qiskitrc-creds-4aac54737333e248.yaml b/releasenotes/notes/load-qiskitrc-creds-4aac54737333e248.yaml new file mode 100644 index 000000000..f940c2c62 --- /dev/null +++ b/releasenotes/notes/load-qiskitrc-creds-4aac54737333e248.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + When initializing :class:`~qiskit_ibm_runtime.QiskitRuntimeService`, and there are no + accounts found, if a qiskitrc file exists, credentials from this file will be saved + as the ``default-ibm-quantum`` account. + + diff --git a/test/account.py b/test/account.py index 9ba25a82b..48262b497 100644 --- a/test/account.py +++ b/test/account.py @@ -99,7 +99,7 @@ def side_effect(self, filename_): class temporary_account_config_file(ContextDecorator): - """Context manager that uses a temporary qiskitrc.""" + """Context manager that uses a temporary json file.""" # pylint: disable=invalid-name @@ -125,6 +125,29 @@ def __exit__(self, *exc): management._DEFAULT_ACCOUNT_CONFIG_JSON_FILE = self.account_config_json_backup +class custom_qiskitrc(ContextDecorator): + """Context manager that uses a temporary qiskitrc.""" + + # pylint: disable=invalid-name + + def __init__(self, contents=b""): + # Create a temporary file with the contents. + self.tmp_file = NamedTemporaryFile() + self.tmp_file.write(contents) + self.tmp_file.flush() + self.default_qiskitrc_file_original = management._QISKITRC_CONFIG_FILE + + def __enter__(self): + # Temporarily modify the default location of the qiskitrc file. + management._QISKITRC_CONFIG_FILE = self.tmp_file.name + return self + + def __exit__(self, *exc): + # Delete the temporary file and restore the default location. + self.tmp_file.close() + management._QISKITRC_CONFIG_FILE = self.default_qiskitrc_file_original + + def get_account_config_contents( name=None, channel="ibm_cloud", diff --git a/test/unit/test_account.py b/test/unit/test_account.py index 2eb978e3a..d298170ec 100644 --- a/test/unit/test_account.py +++ b/test/unit/test_account.py @@ -39,6 +39,7 @@ from ..account import ( get_account_config_contents, temporary_account_config_file, + custom_qiskitrc, no_envs, custom_envs, ) @@ -816,6 +817,24 @@ def test_enable_account_by_name_input_instance(self): self.assertTrue(service._account) self.assertEqual(service._account.instance, instance) + def test_enable_account_by_qiskitrc(self): + """Test initializing account by a qiskitrc file.""" + token = "token-x" + proxies = {"urls": {"https": "localhost:8080"}} + str_contents = f""" + [ibmq] + token = {token} + url = https://auth.quantum-computing.ibm.com/api + verify = True + default_provider = ibm-q/open/main + proxies = {proxies} + """ + with custom_qiskitrc(contents=str.encode(str_contents)): + with temporary_account_config_file(contents={}): + service = FakeRuntimeService() + self.assertTrue(service._account) + self.assertEqual(service._account.token, token) + def test_enable_account_by_channel_input_instance(self): """Test initializing account by channel and input instance.""" instance = uuid.uuid4().hex