Skip to content

Commit

Permalink
Prepare for Cloud authentication support (#39)
Browse files Browse the repository at this point in the history
* black related enhancements (format missing file, add badge)

* prepare public interface and test cases for Cloud auth

* remove unused import to fix pylint error

* fix broken test case

Co-authored-by: Rathish Cholarajan <[email protected]>
  • Loading branch information
daka1510 and rathishcholarajan authored Dec 1, 2021
1 parent bc8632f commit b3fbca7
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 65 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mypy:
mypy --module qiskit_ibm_runtime

style:
black --check qiskit_ibm_runtime test
black --check qiskit_ibm_runtime setup.py test

test:
python -m unittest -v
Expand All @@ -39,4 +39,4 @@ runtime_integration:
python -m unittest -v test/ibm/runtime/test_runtime_integration.py

black:
black qiskit_ibm_runtime test
black qiskit_ibm_runtime setup.py test
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Qiskit IBM Runtime

[![License](https://img.shields.io/github/license/Qiskit/qiskit-ibm-runtime.svg?style=popout-square)](https://opensource.org/licenses/Apache-2.0)[![Push-Test](https://github.com/Qiskit/qiskit-ibm-runtime/actions/workflows/main.yml/badge.svg)](https://github.com/Qiskit/qiskit-ibm-runtime/actions/workflows/main.yml)[![](https://img.shields.io/github/release/Qiskit/qiskit-ibm-runtime.svg?style=popout-square)](https://github.com/Qiskit/qiskit-ibm-runtime/releases)[![](https://img.shields.io/pypi/dm/qiskit-ibm-runtime.svg?style=popout-square)](https://pypi.org/project/qiskit-ibm-runtime/)
[![License](https://img.shields.io/github/license/Qiskit/qiskit-ibm-runtime.svg?style=popout-square)](https://opensource.org/licenses/Apache-2.0)[![Push-Test](https://github.com/Qiskit/qiskit-ibm-runtime/actions/workflows/main.yml/badge.svg)](https://github.com/Qiskit/qiskit-ibm-runtime/actions/workflows/main.yml)[![](https://img.shields.io/github/release/Qiskit/qiskit-ibm-runtime.svg?style=popout-square)](https://github.com/Qiskit/qiskit-ibm-runtime/releases)[![](https://img.shields.io/pypi/dm/qiskit-ibm-runtime.svg?style=popout-square)](https://pypi.org/project/qiskit-ibm-runtime/)[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)


**Qiskit** is an open-source SDK for working with quantum computers at the level of circuits, algorithms, and application modules.

Expand Down
65 changes: 38 additions & 27 deletions qiskit_ibm_runtime/ibm_runtime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,36 @@
"""Qiskit runtime service."""

import copy
import logging
from collections import OrderedDict
from typing import Dict, Callable, Optional, Union, List, Any, Type, Tuple
import json
import logging
import os
import re
import traceback
import warnings
from collections import OrderedDict
from typing import Dict, Callable, Optional, Union, List, Any, Type, Tuple

from qiskit.circuit import QuantumCircuit
from qiskit.providers.backend import BackendV1 as Backend
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.providerutils import filter_backends
from qiskit.transpiler import Layout
from typing_extensions import Literal

from qiskit_ibm_runtime import runtime_job, ibm_backend # pylint: disable=unused-import

from .runtime_job import RuntimeJob
from .runtime_program import RuntimeProgram, ParameterNamespace
from .utils import RuntimeDecoder, to_base64_string, to_python_identifier
from .utils.backend import convert_reservation_data
from .api.clients import AuthClient, VersionClient
from .api.clients.runtime import RuntimeClient
from .api.exceptions import RequestsApiError
from .backendreservation import BackendReservation
from .credentials import Credentials, HubGroupProjectID, discover_credentials
from .credentials.configrc import (
remove_credentials,
read_credentials_from_qiskitrc,
store_credentials,
)
from .credentials.exceptions import HubGroupProjectIDInvalidStateError
from .constants import QISKIT_IBM_RUNTIME_API_URL
from .exceptions import IBMNotAuthorizedError, IBMInputValueError, IBMProviderError
from .exceptions import (
QiskitRuntimeError,
RuntimeDuplicateProgramError,
Expand All @@ -46,22 +55,13 @@
IBMProviderCredentialsInvalidUrl,
IBMProviderValueError,
)
from .program.result_decoder import ResultDecoder
from .api.clients import AuthClient, VersionClient
from .api.clients.runtime import RuntimeClient
from .constants import QISKIT_IBM_RUNTIME_API_URL
from .api.exceptions import RequestsApiError
from .backendreservation import BackendReservation
from .hub_group_project import HubGroupProject # pylint: disable=cyclic-import
from .exceptions import IBMNotAuthorizedError, IBMInputValueError, IBMProviderError
from .credentials import Credentials, HubGroupProjectID, discover_credentials
from .credentials.configrc import (
remove_credentials,
read_credentials_from_qiskitrc,
store_credentials,
)
from .credentials.exceptions import HubGroupProjectIDInvalidStateError
from .program.result_decoder import ResultDecoder
from .runner_result import RunnerResult
from .runtime_job import RuntimeJob
from .runtime_program import RuntimeProgram, ParameterNamespace
from .utils import RuntimeDecoder, to_base64_string, to_python_identifier
from .utils.backend import convert_reservation_data

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -127,13 +127,20 @@ class IBMRuntimeService:
"""

def __init__(
self, token: Optional[str] = None, url: Optional[str] = None, **kwargs: Any
self,
auth: Optional[Literal["cloud", "legacy"]] = None,
token: Optional[str] = None,
locator: Optional[str] = None,
**kwargs: Any,
) -> None:
"""IBMRuntimeService constructor
Args:
token: IBM Quantum token.
url: URL for the IBM Quantum authentication server.
auth: Authentication type. `cloud` or `legacy`. If not specified, the saved default is used.
If there is no default value, and both accounts were saved on disk, the cloud type is
used.
token: Token used for authentication. If not specified, the saved token is used.
locator: The authentication url, if `auth=legacy`. Otherwise the CRN.
**kwargs: Additional settings for the connection:
* proxies (dict): proxy configuration.
Expand All @@ -150,10 +157,14 @@ def __init__(
a valid IBM Quantum authentication URL.
IBMProviderCredentialsInvalidToken: If the `token` is not a valid IBM Quantum token.
"""
# pylint: disable=unused-argument,unsubscriptable-object
super().__init__()
if auth == "cloud":
raise IBMProviderCredentialsNotFound(
"Support for auth type 'cloud' has not yet been implemented."
)

account_credentials, account_preferences = self._resolve_credentials(
token=token, url=url, **kwargs
token=token, url=locator, **kwargs
)
self._initialize_hgps(
credentials=account_credentials, preferences=account_preferences
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/jupyter/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def ibm_quantum_dashboard(self, line="", cell=None) -> None:
"""A Jupyter magic function to enable the dashboard."""
# pylint: disable=unused-argument
try:
service = IBMRuntimeService()
service = IBMRuntimeService(auth="legacy")
except Exception:
raise QiskitError("Could not load IBM Quantum account from the local file.")
_IBM_DASHBOARD.stop_dashboard()
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ numpy>=1.13
urllib3>=1.21.1
python-dateutil>=2.8.0
websocket-client>=1.0.1
typing-extensions>=4.0.0
35 changes: 22 additions & 13 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@
"numpy>=1.13",
"urllib3>=1.21.1",
"python-dateutil>=2.8.0",
"websocket-client>=1.0.1"
"websocket-client>=1.0.1",
"typing-extensions>=4.0.0", # remove when support for Python 3.7 is dropped (use "from typing import" instead)
]

# Handle version.
VERSION_PATH = os.path.join(os.path.dirname(__file__),
"qiskit_ibm_runtime", "VERSION.txt")
VERSION_PATH = os.path.join(
os.path.dirname(__file__), "qiskit_ibm_runtime", "VERSION.txt"
)
with open(VERSION_PATH, "r") as version_file:
VERSION = version_file.read().strip()

# Read long description from README.
README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'README.md')
README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md")
with open(README_PATH) as readme_file:
README = readme_file.read()

Expand All @@ -45,9 +46,9 @@
name="qiskit-ibm-runtime",
version=VERSION,
description="Qiskit IBM Runtime service for accessing the quantum devices and "
"simulators at IBM",
"simulators at IBM",
long_description=README,
long_description_content_type='text/markdown',
long_description_content_type="text/markdown",
url="https://github.com/Qiskit/qiskit-ibm-runtime",
author="Qiskit Development Team",
author_email="[email protected]",
Expand All @@ -67,16 +68,24 @@
"Topic :: Scientific/Engineering",
],
keywords="qiskit sdk quantum api runtime ibm",
packages=setuptools.find_packages(exclude=['test*']),
packages=setuptools.find_packages(exclude=["test*"]),
install_requires=REQUIREMENTS,
include_package_data=True,
python_requires=">=3.7",
zip_safe=False,
extras_require={'visualization': ['matplotlib>=2.1', 'ipywidgets>=7.3.0',
"seaborn>=0.9.0", "plotly>=4.4",
"ipyvuetify>=1.1", "pyperclip>=1.7",
"ipython>=5.0.0", "traitlets!=5.0.5",
"ipyvue>=1.4.1"]},
extras_require={
"visualization": [
"matplotlib>=2.1",
"ipywidgets>=7.3.0",
"seaborn>=0.9.0",
"plotly>=4.4",
"ipyvuetify>=1.1",
"pyperclip>=1.7",
"ipython>=5.0.0",
"traitlets!=5.0.5",
"ipyvue>=1.4.1",
]
},
project_urls={
"Bug Tracker": "https://github.com/Qiskit/qiskit-ibm-runtime/issues",
"Documentation": "https://qiskit.org/documentation/",
Expand Down
8 changes: 4 additions & 4 deletions test/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def requires_providers(func):
def _wrapper(*args, **kwargs):
qe_token = kwargs.pop("qe_token")
qe_url = kwargs.pop("qe_url")
service = IBMRuntimeService(qe_token, qe_url)
service = IBMRuntimeService(auth="legacy", token=qe_token, locator=qe_url)
# Get open access hgp
open_hgp = _get_open_hgp(service)
if not open_hgp:
Expand Down Expand Up @@ -140,7 +140,7 @@ def requires_provider(func):
def _wrapper(*args, **kwargs):
token = kwargs.pop("qe_token")
url = kwargs.pop("qe_url")
service = IBMRuntimeService(token, url)
service = IBMRuntimeService(auth="legacy", token=token, locator=url)
hub, group, project = _get_custom_hgp()
kwargs.update(
{"service": service, "hub": hub, "group": group, "project": project}
Expand Down Expand Up @@ -168,7 +168,7 @@ def requires_private_provider(func):
def _wrapper(*args, **kwargs):
token = kwargs.pop("qe_token")
url = kwargs.pop("qe_url")
service = IBMRuntimeService(token, url)
service = IBMRuntimeService(auth="legacy", token=token, locator=url)
hub, group, project = _get_private_hgp()
kwargs.update(
{"service": service, "hub": hub, "group": group, "project": project}
Expand Down Expand Up @@ -250,7 +250,7 @@ def _wrapper(obj, *args, **kwargs):

def _get_backend(qe_token, qe_url, backend_name):
"""Get the specified backend."""
service = IBMRuntimeService(qe_token, qe_url)
service = IBMRuntimeService(auth="legacy", token=qe_token, locator=qe_url)
_backend = None
hub, group, project = _get_custom_hgp()
if backend_name:
Expand Down
2 changes: 1 addition & 1 deletion test/ibm/runtime/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def setUp(self):
"""Initial test setup."""
super().setUp()
with mock_ibm_provider():
self.service = IBMRuntimeService("abc")
self.service = IBMRuntimeService(auth="legacy", token="abc")
self.service._programs = {}
self.service._default_hgp = mock.MagicMock(spec=HubGroupProject)
self.service._default_hgp.credentials = Credentials(
Expand Down
26 changes: 14 additions & 12 deletions test/ibm/test_ibm_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class TestIBMProviderEnableAccount(IBMTestCase):
def test_provider_init_token(self, qe_token, qe_url):
"""Test initializing IBMRuntimeService with only API token."""
# pylint: disable=unused-argument
service = IBMRuntimeService(token=qe_token)
service = IBMRuntimeService(auth="legacy", token=qe_token)
self.assertIsInstance(service, IBMRuntimeService)
self.assertEqual(service._default_hgp.credentials.token, qe_token)

Expand All @@ -67,7 +67,9 @@ def test_pass_unreachable_proxy(self, qe_token, qe_url):
}
}
with self.assertRaises(RequestsApiError) as context_manager:
IBMRuntimeService(qe_token, qe_url, proxies=proxies)
IBMRuntimeService(
auth="legacy", token=qe_token, locator=qe_url, proxies=proxies
)
self.assertIn("ProxyError", str(context_manager.exception))

def test_provider_init_non_auth_url(self):
Expand All @@ -76,7 +78,7 @@ def test_provider_init_non_auth_url(self):
qe_url = API_URL

with self.assertRaises(IBMProviderCredentialsInvalidUrl) as context_manager:
IBMRuntimeService(token=qe_token, url=qe_url)
IBMRuntimeService(auth="legacy", token=qe_token, locator=qe_url)

self.assertIn("authentication URL", str(context_manager.exception))

Expand All @@ -86,7 +88,7 @@ def test_provider_init_non_auth_url_with_hub(self):
qe_url = API_URL + "/Hubs/X/Groups/Y/Projects/Z"

with self.assertRaises(IBMProviderCredentialsInvalidUrl) as context_manager:
IBMRuntimeService(token=qe_token, url=qe_url)
IBMRuntimeService(auth="legacy", token=qe_token, locator=qe_url)

self.assertIn("authentication URL", str(context_manager.exception))

Expand All @@ -95,7 +97,7 @@ def test_provider_init_no_credentials(self):
with custom_qiskitrc(), self.assertRaises(
IBMProviderCredentialsNotFound
) as context_manager, no_envs(CREDENTIAL_ENV_VARS):
IBMRuntimeService()
IBMRuntimeService(auth="legacy")

self.assertIn(
"No IBM Quantum credentials found.", str(context_manager.exception)
Expand All @@ -112,7 +114,7 @@ def test_discover_backend_failed(self, qe_token, qe_url):
with self.assertLogs(
hub_group_project.logger, level="WARNING"
) as context_manager:
IBMRuntimeService(qe_token, qe_url)
IBMRuntimeService(auth="legacy", token=qe_token, locator=qe_url)
self.assertIn("bad_backend", str(context_manager.output))


Expand Down Expand Up @@ -144,7 +146,7 @@ def test_provider_init_saved_account(self, qe_token, qe_url):

with custom_qiskitrc(), no_envs(CREDENTIAL_ENV_VARS):
IBMRuntimeService.save_account(qe_token, url=qe_url)
service = IBMRuntimeService()
service = IBMRuntimeService(auth="legacy")

self.assertIsInstance(service, IBMRuntimeService)
self.assertEqual(service._default_hgp.credentials.token, qe_token)
Expand Down Expand Up @@ -223,7 +225,7 @@ def test_load_account_saved_provider(self, qe_token, qe_url):
group=non_default_hgp.credentials.group,
project=non_default_hgp.credentials.project,
)
saved_provider = IBMRuntimeService()
saved_provider = IBMRuntimeService(auth="legacy")
if saved_provider._default_hgp != non_default_hgp:
# Prevent tokens from being logged.
saved_provider._default_hgp.credentials.token = None
Expand Down Expand Up @@ -268,7 +270,7 @@ def test_load_saved_account_invalid_hgp(self, qe_token, qe_url):
group=hgp_id.group,
project=hgp_id.project,
)
IBMRuntimeService()
IBMRuntimeService(auth="legacy")

self.assertIn(
"No hub/group/project matches the specified criteria",
Expand Down Expand Up @@ -296,13 +298,13 @@ def test_load_saved_account_invalid_hgp_format(self):
_file.write("default_provider = {}".format(invalid_hgp))
# Ensure an error is raised if the stored provider is in an invalid format.
with self.assertRaises(IBMProviderError) as context_manager:
IBMRuntimeService()
IBMRuntimeService(auth="legacy")
self.assertIn(error_message, str(context_manager.exception))

@requires_qe_access
def test_active_account(self, qe_token, qe_url):
"""Test get active account"""
service = IBMRuntimeService(qe_token, qe_url)
service = IBMRuntimeService(auth="legacy", token=qe_token, locator=qe_url)
active_account = service.active_account()
self.assertIsNotNone(active_account)
self.assertEqual(active_account["token"], qe_token)
Expand Down Expand Up @@ -330,7 +332,7 @@ class TestIBMProviderHubGroupProject(IBMTestCase):
@requires_qe_access
def _initialize_provider(self, qe_token=None, qe_url=None):
"""Initialize and return provider."""
return IBMRuntimeService(qe_token, qe_url)
return IBMRuntimeService(auth="legacy", token=qe_token, locator=qe_url)

def setUp(self):
"""Initial test setup."""
Expand Down
7 changes: 6 additions & 1 deletion test/ibm/test_proxies.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ def tearDown(self):
@requires_qe_access
def test_proxies_ibm_account(self, qe_token, qe_url):
"""Should reach the proxy using account.enable."""
service = IBMRuntimeService(qe_token, qe_url, proxies={"urls": VALID_PROXIES})
service = IBMRuntimeService(
auth="legacy",
token=qe_token,
locator=qe_url,
proxies={"urls": VALID_PROXIES},
)

self.proxy_process.terminate() # kill to be able of reading the output

Expand Down
Loading

0 comments on commit b3fbca7

Please sign in to comment.