Skip to content

Commit

Permalink
Use qiskitrc credentials if default config doesn't exist (#370)
Browse files Browse the repository at this point in the history
* copy qiskitrc creds

* add reno

* add unit test

* move save_config call

* add instance

* update docstring

* add proxies and verify to config

* update test case

* set overwrite to false

Co-authored-by: Rathish Cholarajan <rathish.c@ibm.com>
  • Loading branch information
kt474 and rathishcholarajan committed Jun 22, 2022
1 parent cf0c05a commit bac910b
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 2 deletions.
31 changes: 30 additions & 1 deletion qiskit_ibm_runtime/accounts/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions qiskit_ibm_runtime/accounts/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/load-qiskitrc-creds-4aac54737333e248.yaml
Original file line number Diff line number Diff line change
@@ -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.
25 changes: 24 additions & 1 deletion test/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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",
Expand Down
19 changes: 19 additions & 0 deletions test/unit/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from ..account import (
get_account_config_contents,
temporary_account_config_file,
custom_qiskitrc,
no_envs,
custom_envs,
)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit bac910b

Please sign in to comment.