Skip to content
This repository was archived by the owner on Jul 28, 2023. It is now read-only.

Autocomplete of backend names #303

Merged
merged 10 commits into from
Aug 28, 2019
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ The format is based on [Keep a Changelog].

## [UNRELEASED]

### Added

- Added support for autocompleting backend names. A user can now use
`AccountProvider.provider_backends.<tab>` to see a list of backend names (\#303).

### Changed

- The `IBMQBackend.run()` function now accepts an optional `job_name` parameter.
Expand Down
19 changes: 16 additions & 3 deletions qiskit/providers/ibmq/accountprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .circuits import CircuitsManager
from .ibmqbackend import IBMQBackend, IBMQSimulator
from .credentials import Credentials
from .providerbackends import ProviderBackends


logger = logging.getLogger(__name__)
Expand All @@ -39,6 +40,11 @@ class AccountProvider(BaseProvider):
def __init__(self, credentials: Credentials, access_token: str) -> None:
"""Return a new AccountProvider.

The ``provider_backends`` attribute can be used to autocomplete
backend names, by pressing ``tab`` after
``AccountProvider.provider_backends.``. Note that this feature may
not be available if an error occurs during backend discovery.

Args:
credentials (Credentials): IBM Q Experience credentials.
access_token (str): access token for IBM Q Experience.
Expand All @@ -58,10 +64,13 @@ def __init__(self, credentials: Credentials, access_token: str) -> None:
# access.
self._backends = None

self.provider_backends = ProviderBackends(self)

def backends(
self,
name: Optional[str] = None,
filters: Optional[Callable[[List[IBMQBackend]], bool]] = None,
timeout: Optional[float] = None,
**kwargs: Dict
) -> List[IBMQBackend]:
"""Return all backends accessible via this provider, subject to optional filtering.
Expand All @@ -71,6 +80,7 @@ def backends(
filters (callable): more complex filters, such as lambda functions
e.g. AccountProvider.backends(
filters=lambda b: b.configuration['n_qubits'] > 5)
timeout (float or None): number of seconds to wait for backend discovery.
kwargs: simple filters specifying a true/false criteria in the
backend configuration or backend status or provider credentials
e.g. AccountProvider.backends(n_qubits=5, operational=True)
Expand All @@ -80,7 +90,7 @@ def backends(
"""
# pylint: disable=arguments-differ
if self._backends is None:
self._backends = self._discover_remote_backends()
self._backends = self._discover_remote_backends(timeout=timeout)

backends = self._backends.values()

Expand All @@ -94,15 +104,18 @@ def backends(

return filter_backends(backends, filters=filters, **kwargs)

def _discover_remote_backends(self) -> Dict[str, IBMQBackend]:
def _discover_remote_backends(self, timeout: Optional[float] = None) -> Dict[str, IBMQBackend]:
"""Return the remote backends available.

Args:
timeout (float or None): number of seconds to wait for the discovery.

Returns:
dict[str:IBMQBackend]: a dict of the remote backend instances,
keyed by backend name.
"""
ret = OrderedDict()
configs_list = self._api.available_backends()
configs_list = self._api.available_backends(timeout=timeout)
for raw_config in configs_list:
# Make sure the raw_config is of proper type
if not isinstance(raw_config, dict):
Expand Down
11 changes: 7 additions & 4 deletions qiskit/providers/ibmq/api_v2/clients/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,16 @@ def __init__(

# Backend-related public functions.

def list_backends(self) -> List[Dict[str, Any]]:
def list_backends(self, timeout: Optional[float] = None) -> List[Dict[str, Any]]:
"""Return the list of backends.

Args:
timeout (float or None): number of seconds to wait for the request.

Returns:
list[dict]: a list of backends.
"""
return self.client_api.backends()
return self.client_api.backends(timeout=timeout)

def backend_status(self, backend_name: str) -> Dict[str, Any]:
"""Return the status of a backend.
Expand Down Expand Up @@ -367,9 +370,9 @@ def backend_defaults(self, backend):
# pylint: disable=missing-docstring
return self.backend_pulse_defaults(backend)

def available_backends(self):
def available_backends(self, timeout=None):
# pylint: disable=missing-docstring
return self.list_backends()
return self.list_backends(timeout=timeout)

def get_job(self, id_job, exclude_fields=None, include_fields=None):
# pylint: disable=missing-docstring
Expand Down
13 changes: 10 additions & 3 deletions qiskit/providers/ibmq/api_v2/rest/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,17 @@ def job(self, job_id: str) -> Job:
"""
return Job(self.session, job_id)

def backends(self) -> List[Dict[str, Any]]:
"""Return the list of backends."""
def backends(self, timeout: Optional[float] = None) -> List[Dict[str, Any]]:
"""Return the list of backends.

Args:
timeout (float or None): number of seconds to wait for the request.

Returns:
list[dict]: json response.
"""
url = self.get_url('backends')
return self.session.get(url).json()
return self.session.get(url, timeout=timeout).json()

def hubs(self) -> List[Dict[str, Any]]:
"""Return the list of hubs available to the user."""
Expand Down
68 changes: 68 additions & 0 deletions qiskit/providers/ibmq/providerbackends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Backend namespace for an IBM Quantum Experience account provider."""

import re
import keyword
from types import SimpleNamespace

from .api_v2.exceptions import RequestsApiError


class ProviderBackends(SimpleNamespace):
"""Backend namespace for an IBM Quantum Experience account provider."""

def __init__(self, provider):
"""Creates a new ProviderBackends instance.
Args:
provider (AccountProvider): IBM Q Experience account provider
"""
self._provider = provider
self._initialized = False
super().__init__()

def _discover_backends(self):
"""Discovers the remote backends if not already known."""
if not self._initialized:
try:
# Python identifiers can only contain alphanumeric characters
# and underscores and cannot start with a digit.
pattern = re.compile(r"\W|^(?=\d)", re.ASCII)
for backend in self._provider.backends(timeout=3):
backend_name = backend.name()

# Make it a valid identifier
if not backend_name.isidentifier():
backend_name = re.sub(pattern, '_', backend_name)

# Append _ if is keyword or duplicate
while keyword.iskeyword(backend_name) or backend_name in self.__dict__:
backend_name += '_'

setattr(self, backend_name, backend)
self._initialized = True
except RequestsApiError:
# Ignore any networking errors since this is a convenience
# feature meant for interactive sessions.
pass

def __dir__(self):
self._discover_backends()
return super().__dir__()

def __getattr__(self, item):
self._discover_backends()
return super().__getattribute__(item)
19 changes: 19 additions & 0 deletions test/ibmq/test_ibmq_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

"""Tests for all IBMQ backends."""
from datetime import datetime
from unittest.mock import patch
from requests.exceptions import Timeout

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
from qiskit.providers.ibmq import IBMQProvider
from qiskit.providers.exceptions import QiskitBackendNotFoundError
Expand All @@ -25,6 +28,7 @@
from qiskit.test import slow_test, providers
from qiskit.compiler import assemble, transpile
from qiskit.providers.models.backendproperties import BackendProperties
from qiskit.providers.ibmq.api_v2.session import Session

from ..decorators import (requires_qe_access,
requires_classic_api,
Expand Down Expand Up @@ -190,3 +194,18 @@ def test_remote_backend_properties_filter_date(self):
self.assertLessEqual(last_update_date, datetime_filter)
else:
self.assertEqual(properties, None)

def test_provider_backends(self):
"""Test provider_backends have correct attributes."""
provider_backends = {back for back in dir(self.provider.provider_backends)
if not back.startswith('_')}
backends = {back.name() for back in self.provider.backends()}
self.assertEqual(provider_backends, backends)

def test_provider_backends_timeout(self):
"""Test provider_backends timeout."""
with patch.object(Session, "request", side_effect=Timeout):
provider_backends = {
back for back in dir(self.provider.provider_backends)
if not back.startswith('_')}
self.assertEqual(len(provider_backends), 0)