From 381970120239443ed82c7ed77dbaa95572d9003c Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Tue, 8 Jan 2019 12:02:35 +0100 Subject: [PATCH 1/3] Add field inclusion/exclusion to get_job Add `exclude_fields` and `include_fields` to connector.get_job(), following the changes in the `qiskit-sdk-api` unmerged branch. Add a basic test for exclusion. Bump marshmalllow version to keep in sync with terra. --- qiskit/providers/ibmq/api/ibmqconnector.py | 31 +++++++++++++++++++++- requirements.txt | 2 +- setup.py | 2 +- test/ibmq/test_ibmq_connector.py | 24 ++++++++++++++--- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/qiskit/providers/ibmq/api/ibmqconnector.py b/qiskit/providers/ibmq/api/ibmqconnector.py index 30c4d9923..6e695c697 100644 --- a/qiskit/providers/ibmq/api/ibmqconnector.py +++ b/qiskit/providers/ibmq/api/ibmqconnector.py @@ -553,8 +553,29 @@ def run_job(self, job, backend='simulator', shots=1, return job def get_job(self, id_job, hub=None, group=None, project=None, + exclude_fields=None, include_fields=None, access_token=None, user_id=None): """Get the information about a job, by its id.""" + + def build_url_filter(excluded_fields, included_fields): + """Return a URL filter based on included and excluded fields.""" + excluded_fields = excluded_fields or [] + included_fields = included_fields or [] + fields_bool = {} + + # Build a map of fields to bool. + for field_ in excluded_fields: + fields_bool[field_] = False + for field_ in included_fields: + fields_bool[field_] = True + + if 'properties' in fields_bool: + fields_bool['calibration'] = fields_bool.pop('properties') + + if fields_bool: + return '&filter=' + json.dumps({'fields': fields_bool}) + return '' + if access_token: self.req.credential.set_token(access_token) if user_id: @@ -570,7 +591,11 @@ def get_job(self, id_job, hub=None, group=None, project=None, url += '/' + id_job - job = self.req.get(url) + job = self.req.get(url, params=build_url_filter(exclude_fields, + include_fields)) + + if 'calibration' in job: + job['properties'] = job.pop('calibration') if 'qObjectResult' in job: # If the job is using Qobj, return the qObjectResult directly, @@ -619,6 +644,10 @@ def get_jobs(self, limit=10, skip=0, backend=None, only_completed=False, url_filter = url_filter + json.dumps(query) jobs = self.req.get(url, url_filter) + for job in jobs: + if 'calibration' in job: + job['properties'] = job.pop('calibration') + return jobs def get_status_job(self, id_job, hub=None, group=None, project=None, diff --git a/requirements.txt b/requirements.txt index 02c942b7d..b5eaca6dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ qiskit-terra>=0.7,<0.8 -marshmallow>=2.16.3,<3 +marshmallow>=2.17.0,<3 requests>=2.19 requests-ntlm>=1.1.0 diff --git a/setup.py b/setup.py index 3ecf8d159..1df628583 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ requirements = [ "qiskit-terra>=0.7,<0.8", - "marshmallow>=2.16.3,<3", + "marshmallow>=2.17.0,<3", "requests>=2.19", "requests-ntlm>=1.1.0", ] diff --git a/test/ibmq/test_ibmq_connector.py b/test/ibmq/test_ibmq_connector.py index a972b9084..35146cc21 100644 --- a/test/ibmq/test_ibmq_connector.py +++ b/test/ibmq/test_ibmq_connector.py @@ -23,6 +23,11 @@ def setUp(self): self.qasms = [{'qasm': SAMPLE_QASM_1}, {'qasm': SAMPLE_QASM_2}] + @staticmethod + def _get_api(qe_token, qe_url): + """Helper for instantating an IBMQConnector.""" + return IBMQConnector(qe_token, config={'url': qe_url}) + @requires_qe_access def test_api_auth_token(self, qe_token, qe_url): """Authentication with IBMQ Platform.""" @@ -114,10 +119,21 @@ def test_qx_api_version(self, qe_token, qe_url): version = api.api_version() self.assertGreaterEqual(int(version.split(".")[0]), 4) - @staticmethod - def _get_api(qe_token, qe_url): - """Helper for instantating an IBMQConnector.""" - return IBMQConnector(qe_token, config={'url': qe_url}) + @requires_qe_access + def test_get_job_includes(self, qe_token, qe_url): + """Check the field includes parameter for get_job.""" + api = self._get_api(qe_token, qe_url) + + # Run a job and get its id. + backend = 'ibmq_qasm_simulator' + shots = 1 + job = api.run_job(self.qasms, backend, shots) + job_id = job['id'] + + # Get the job, excluding a parameter. + self.assertIn('userId', job) + job_excluded = api.get_job(job_id, exclude_fields=['userId']) + self.assertNotIn('userId', job_excluded) class TestAuthentication(QiskitTestCase): From 568932f40661e66521392148f86ca8e2f3309fa3 Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Fri, 11 Jan 2019 14:45:47 +0100 Subject: [PATCH 2/3] Split IBMQConnector into two files, tweaks Move the Request and Credentials `IBMQConnector` classes to its own file, along with some tweaks with default parameters and removing unused code. --- qiskit/providers/ibmq/api/ibmqconnector.py | 435 +-------------------- qiskit/providers/ibmq/api/utils.py | 407 +++++++++++++++++++ 2 files changed, 421 insertions(+), 421 deletions(-) create mode 100644 qiskit/providers/ibmq/api/utils.py diff --git a/qiskit/providers/ibmq/api/ibmqconnector.py b/qiskit/providers/ibmq/api/ibmqconnector.py index 6e695c697..96ceb7363 100644 --- a/qiskit/providers/ibmq/api/ibmqconnector.py +++ b/qiskit/providers/ibmq/api/ibmqconnector.py @@ -9,18 +9,11 @@ import json import logging -import re -import time -from urllib import parse -import requests -from requests_ntlm import HttpNtlmAuth - -from .exceptions import (ApiError, CredentialsError, RegisterSizeError, - BadBackendError) +from .exceptions import CredentialsError, BadBackendError +from .utils import Request logger = logging.getLogger(__name__) -CLIENT_APPLICATION = 'qiskit-api-py' def get_job_url(config, hub=None, group=None, project=None): @@ -56,399 +49,11 @@ def get_backends_url(config, hub, group, project): return '/Backends/v/1' -class _Credentials: - """Credentials class that manages the tokens.""" - - config_base = {'url': 'https://quantumexperience.ng.bluemix.net/api'} - - def __init__(self, token, config=None, verify=True, proxy_urls=None, - ntlm_credentials=None): - self.token_unique = token - self.verify = verify - self.config = config - self.proxy_urls = proxy_urls - self.ntlm_credentials = ntlm_credentials - - # Set the extra arguments to requests (proxy and auth). - self.extra_args = {} - if self.proxy_urls: - self.extra_args['proxies'] = self.proxy_urls - if self.ntlm_credentials: - self.extra_args['auth'] = HttpNtlmAuth( - self.ntlm_credentials['username'], - self.ntlm_credentials['password']) - - if not verify: - # pylint: disable=import-error - import requests.packages.urllib3 as urllib3 - urllib3.disable_warnings() - print('-- Ignoring SSL errors. This is not recommended --') - if self.config and ("url" not in self.config): - self.config["url"] = self.config_base["url"] - elif not self.config: - self.config = self.config_base - - self.data_credentials = {} - if token: - self.obtain_token(config=self.config) - else: - access_token = self.config.get('access_token', None) - if access_token: - user_id = self.config.get('user_id', None) - if access_token: - self.set_token(access_token) - if user_id: - self.set_user_id(user_id) - else: - self.obtain_token(config=self.config) - - def obtain_token(self, config=None): - """Obtain the token to access to QX Platform. - - Raises: - CredentialsError: when token is invalid or the user has not - accepted the license. - ApiError: when the response from the server couldn't be parsed. - """ - client_application = CLIENT_APPLICATION - if self.config and ("client_application" in self.config): - client_application += ':' + self.config["client_application"] - headers = {'x-qx-client-application': client_application} - - if self.token_unique: - try: - response = requests.post(str(self.config.get('url') + - "/users/loginWithToken"), - data={'apiToken': self.token_unique}, - verify=self.verify, - headers=headers, - **self.extra_args) - except requests.RequestException as ex: - raise ApiError('error during login: %s' % str(ex)) - elif config and ("email" in config) and ("password" in config): - email = config.get('email', None) - password = config.get('password', None) - credentials = { - 'email': email, - 'password': password - } - try: - response = requests.post(str(self.config.get('url') + - "/users/login"), - data=credentials, - verify=self.verify, - headers=headers, - **self.extra_args) - except requests.RequestException as ex: - raise ApiError('error during login: %s' % str(ex)) - else: - raise CredentialsError('invalid token') - - if response.status_code == 401: - error_message = None - try: - # For 401: ACCEPT_LICENSE_REQUIRED, a detailed message is - # present in the response and passed to the exception. - error_message = response.json()['error']['message'] - except Exception: # pylint: disable=broad-except - pass - - if error_message: - raise CredentialsError('error during login: %s' % error_message) - else: - raise CredentialsError('invalid token') - try: - response.raise_for_status() - self.data_credentials = response.json() - except (requests.HTTPError, ValueError) as ex: - raise ApiError('error during login: %s' % str(ex)) - - if self.get_token() is None: - raise CredentialsError('invalid token') - - def get_token(self): - """Return the Authenticated Token to connect with QX Platform.""" - return self.data_credentials.get('id', None) - - def get_user_id(self): - """Return the user id in QX platform.""" - return self.data_credentials.get('userId', None) - - def get_config(self): - """Return the configuration that was set for this Credentials.""" - return self.config - - def set_token(self, access_token): - """Set the Access Token to connect with QX Platform API.""" - self.data_credentials['id'] = access_token - - def set_user_id(self, user_id): - """Set the user id to connect with QX Platform API.""" - self.data_credentials['userId'] = user_id - - -class _Request: - """Request class that performs the HTTP calls. - - Note: - Set the proxy information, if present, from the configuration, - with the following format:: - - config = { - 'proxies': { - # If using 'urls', assume basic auth or no auth. - 'urls': { - 'http': 'http://user:password@1.2.3.4:5678', - 'https': 'http://user:password@1.2.3.4:5678', - } - # If using 'ntlm', assume NTLM authentication. - 'username_ntlm': 'domain\\username', - 'password_ntlm': 'password' - } - } - """ - - def __init__(self, token, config=None, verify=True, retries=5, - timeout_interval=1.0): - self.verify = verify - self.client_application = CLIENT_APPLICATION - self.config = config - self.errors_not_retry = [401, 403, 413] - - # Set the basic proxy settings, if present. - self.proxy_urls = None - self.ntlm_credentials = None - if config and 'proxies' in config: - if 'urls' in config['proxies']: - self.proxy_urls = self.config['proxies']['urls'] - if 'username_ntlm' and 'password_ntlm' in config['proxies']: - self.ntlm_credentials = { - 'username': self.config['proxies']['username_ntlm'], - 'password': self.config['proxies']['password_ntlm'] - } - - # Set the extra arguments to requests (proxy and auth). - self.extra_args = {} - if self.proxy_urls: - self.extra_args['proxies'] = self.proxy_urls - if self.ntlm_credentials: - self.extra_args['auth'] = HttpNtlmAuth( - self.ntlm_credentials['username'], - self.ntlm_credentials['password']) - - if self.config and ("client_application" in self.config): - self.client_application += ':' + self.config["client_application"] - self.credential = _Credentials(token, self.config, verify, - proxy_urls=self.proxy_urls, - ntlm_credentials=self.ntlm_credentials) - - if not isinstance(retries, int): - raise TypeError('post retries must be positive integer') - self.retries = retries - self.timeout_interval = timeout_interval - self.result = None - self._max_qubit_error_re = re.compile( - r".*registers exceed the number of qubits, " - r"it can\'t be greater than (\d+).*") - - def check_token(self, response): - """Check is the user's token is valid.""" - if response.status_code == 401: - self.credential.obtain_token(config=self.config) - return False - return True - - def post(self, path, params='', data=None): - """POST Method Wrapper of the REST API.""" - self.result = None - data = data or {} - headers = {'Content-Type': 'application/json', - 'x-qx-client-application': self.client_application} - url = str(self.credential.config['url'] + path + '?access_token=' + - self.credential.get_token() + params) - retries = self.retries - while retries > 0: - response = requests.post(url, data=data, headers=headers, - verify=self.verify, **self.extra_args) - if not self.check_token(response): - response = requests.post(url, data=data, headers=headers, - verify=self.verify, - **self.extra_args) - - if self._response_good(response): - if self.result: - return self.result - elif retries < 2: - return response.json() - else: - retries -= 1 - else: - retries -= 1 - time.sleep(self.timeout_interval) - - # timed out - raise ApiError(usr_msg='Failed to get proper ' + - 'response from backend.') - - def put(self, path, params='', data=None): - """PUT Method Wrapper of the REST API.""" - self.result = None - data = data or {} - headers = {'Content-Type': 'application/json', - 'x-qx-client-application': self.client_application} - url = str(self.credential.config['url'] + path + '?access_token=' + - self.credential.get_token() + params) - retries = self.retries - while retries > 0: - response = requests.put(url, data=data, headers=headers, - verify=self.verify, **self.extra_args) - if not self.check_token(response): - response = requests.put(url, data=data, headers=headers, - verify=self.verify, - **self.extra_args) - if self._response_good(response): - if self.result: - return self.result - elif retries < 2: - return response.json() - else: - retries -= 1 - else: - retries -= 1 - time.sleep(self.timeout_interval) - # timed out - raise ApiError(usr_msg='Failed to get proper ' + - 'response from backend.') - - def get(self, path, params='', with_token=True): - """GET Method Wrapper of the REST API.""" - self.result = None - access_token = '' - if with_token: - access_token = self.credential.get_token() or '' - if access_token: - access_token = '?access_token=' + str(access_token) - url = self.credential.config['url'] + path + access_token + params - retries = self.retries - headers = {'x-qx-client-application': self.client_application} - while retries > 0: # Repeat until no error - response = requests.get(url, verify=self.verify, headers=headers, - **self.extra_args) - if not self.check_token(response): - response = requests.get(url, verify=self.verify, - headers=headers, **self.extra_args) - if self._response_good(response): - if self.result: - return self.result - elif retries < 2: - return response.json() - else: - retries -= 1 - else: - retries -= 1 - time.sleep(self.timeout_interval) - # timed out - raise ApiError(usr_msg='Failed to get proper ' + - 'response from backend.') - - def _sanitize_url(self, url): - """Strip any tokens or actual paths from url. - - Args: - url (str): The url to sanitize - - Returns: - str: The sanitized url - """ - return parse.urlparse(url).path - - def _response_good(self, response): - """check response. - - Args: - response (requests.Response): HTTP response. - - Returns: - bool: True if the response is good, else False. - - Raises: - ApiError: response isn't formatted properly. - """ - - url = self._sanitize_url(response.url) - - if response.status_code != requests.codes.ok: - logger.warning('Got a %s code response to %s: %s', - response.status_code, - url, - response.text) - if response.status_code in self.errors_not_retry: - raise ApiError(usr_msg='Got a {} code response to {}: {}'.format( - response.status_code, - url, - response.text)) - else: - return self._parse_response(response) - try: - if str(response.headers['content-type']).startswith("text/html;"): - self.result = response.text - return True - else: - self.result = response.json() - except (json.JSONDecodeError, ValueError): - usr_msg = 'device server returned unexpected http response' - dev_msg = usr_msg + ': ' + response.text - raise ApiError(usr_msg=usr_msg, dev_msg=dev_msg) - if not isinstance(self.result, (list, dict)): - msg = ('JSON not a list or dict: url: {0},' - 'status: {1}, reason: {2}, text: {3}') - raise ApiError( - usr_msg=msg.format(url, - response.status_code, - response.reason, response.text)) - if ('error' not in self.result or - ('status' not in self.result['error'] or - self.result['error']['status'] != 400)): - return True - - logger.warning("Got a 400 code JSON response to %s", url) - return False - - def _parse_response(self, response): - """parse text of response for HTTP errors. - - This parses the text of the response to decide whether to - retry request or raise exception. At the moment this only - detects an exception condition. - - Args: - response (Response): requests.Response object - - Returns: - bool: False if the request should be retried, True - if not. - - Raises: - RegisterSizeError: if invalid device register size. - """ - # convert error messages into exceptions - mobj = self._max_qubit_error_re.match(response.text) - if mobj: - raise RegisterSizeError( - 'device register size must be <= {}'.format(mobj.group(1))) - return True - - class IBMQConnector: """Connector class that handles the requests to the IBMQ platform. This class exposes a Python API for making requests to the IBMQ platform. """ - __names_backend_ibmqxv2 = ['ibmqx5qv2', 'ibmqx2', 'qx5qv2', 'qx5q', 'real'] - __names_backend_ibmqxv3 = ['ibmqx3'] - __names_backend_simulator = ['simulator', 'sim_trivial_2', - 'ibmqx_qasm_simulator', 'ibmq_qasm_simulator'] - def __init__(self, token=None, config=None, verify=True): """ If verify is set to false, ignore SSL certificate errors """ self.config = config @@ -472,26 +77,17 @@ def __init__(self, token=None, config=None, verify=True): self.config['hub'] = hub self.config['url'] = url_parsed[0] + '/api' - self.req = _Request(token, config=config, verify=verify) + self.req = Request(token, config=config, verify=verify) - def _check_backend(self, backend, endpoint): + def _check_backend(self, backend_name): """Check if the name of a backend is valid to run in QX Platform.""" - # First check against hacks for old backend names - original_backend = backend - backend = backend.lower() - if endpoint == 'experiment': - if backend in self.__names_backend_ibmqxv2: - return 'real' - elif backend in self.__names_backend_ibmqxv3: - return 'ibmqx3' - elif backend in self.__names_backend_simulator: - return 'sim_trivial_2' + backend_name = backend_name.lower() # Check for new-style backends backends = self.available_backends() for backend_ in backends: - if backend_.get('backend_name', '') == original_backend: - return original_backend + if backend_.get('backend_name', '') == backend_name: + return backend_name # backend unrecognized return None @@ -499,7 +95,7 @@ def check_credentials(self): """Check if the user has permission in QX platform.""" return bool(self.req.credential.get_token()) - def run_job(self, job, backend='simulator', shots=1, + def run_job(self, job, backend, shots=1, max_credits=None, seed=None, hub=None, group=None, project=None, hpc=None, access_token=None, user_id=None): """Execute a job.""" @@ -510,7 +106,7 @@ def run_job(self, job, backend='simulator', shots=1, if not self.check_credentials(): return {"error": "Not credentials valid"} - backend_type = self._check_backend(backend, 'job') + backend_type = self._check_backend(backend) if not backend_type: raise BadBackendError(backend) @@ -729,13 +325,13 @@ def cancel_job(self, id_job, hub=None, group=None, project=None, return res - def backend_status(self, backend='ibmqx4', access_token=None, user_id=None): + def backend_status(self, backend, access_token=None, user_id=None): """Get the status of a chip.""" if access_token: self.req.credential.set_token(access_token) if user_id: self.req.credential.set_user_id(user_id) - backend_type = self._check_backend(backend, 'status') + backend_type = self._check_backend(backend) if not backend_type: raise BadBackendError(backend) @@ -762,8 +358,8 @@ def backend_status(self, backend='ibmqx4', access_token=None, user_id=None): return ret - def backend_properties(self, backend='ibmqx4', hub=None, - access_token=None, user_id=None): + def backend_properties(self, backend, hub=None, access_token=None, + user_id=None): """Get the parameters of calibration of a real chip.""" if access_token: self.req.credential.set_token(access_token) @@ -772,14 +368,11 @@ def backend_properties(self, backend='ibmqx4', hub=None, if not self.check_credentials(): raise CredentialsError('credentials invalid') - backend_type = self._check_backend(backend, 'calibration') + backend_type = self._check_backend(backend) if not backend_type: raise BadBackendError(backend) - if backend_type in self.__names_backend_simulator: - return {} - url = get_backend_properties_url(self.config, backend_type, hub) ret = self.req.get(url, params="&version=1") diff --git a/qiskit/providers/ibmq/api/utils.py b/qiskit/providers/ibmq/api/utils.py new file mode 100644 index 000000000..e914b966f --- /dev/null +++ b/qiskit/providers/ibmq/api/utils.py @@ -0,0 +1,407 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Utilities for IBM Q API connector.""" + +import json +import logging +import re +import time +from urllib import parse + +import requests +from requests_ntlm import HttpNtlmAuth + +from .exceptions import (ApiError, CredentialsError, RegisterSizeError) + + +logger = logging.getLogger(__name__) + +CLIENT_APPLICATION = 'qiskit-api-py' + + +class Credentials: + """Credentials class that manages the tokens.""" + + config_base = {'url': 'https://quantumexperience.ng.bluemix.net/api'} + + def __init__(self, token, config=None, verify=True, proxy_urls=None, + ntlm_credentials=None): + self.token_unique = token + self.verify = verify + self.config = config + self.proxy_urls = proxy_urls + self.ntlm_credentials = ntlm_credentials + + # Set the extra arguments to requests (proxy and auth). + self.extra_args = {} + if self.proxy_urls: + self.extra_args['proxies'] = self.proxy_urls + if self.ntlm_credentials: + self.extra_args['auth'] = HttpNtlmAuth( + self.ntlm_credentials['username'], + self.ntlm_credentials['password']) + + if not verify: + # pylint: disable=import-error + import requests.packages.urllib3 as urllib3 + urllib3.disable_warnings() + print('-- Ignoring SSL errors. This is not recommended --') + if self.config and ("url" not in self.config): + self.config["url"] = self.config_base["url"] + elif not self.config: + self.config = self.config_base + + self.data_credentials = {} + if token: + self.obtain_token(config=self.config) + else: + access_token = self.config.get('access_token', None) + if access_token: + user_id = self.config.get('user_id', None) + if access_token: + self.set_token(access_token) + if user_id: + self.set_user_id(user_id) + else: + self.obtain_token(config=self.config) + + def obtain_token(self, config=None): + """Obtain the token to access to QX Platform. + + Raises: + CredentialsError: when token is invalid or the user has not + accepted the license. + ApiError: when the response from the server couldn't be parsed. + """ + client_application = CLIENT_APPLICATION + if self.config and ("client_application" in self.config): + client_application += ':' + self.config["client_application"] + headers = {'x-qx-client-application': client_application} + + if self.token_unique: + try: + response = requests.post(str(self.config.get('url') + + "/users/loginWithToken"), + data={'apiToken': self.token_unique}, + verify=self.verify, + headers=headers, + **self.extra_args) + except requests.RequestException as ex: + raise ApiError('error during login: %s' % str(ex)) + elif config and ("email" in config) and ("password" in config): + email = config.get('email', None) + password = config.get('password', None) + credentials = { + 'email': email, + 'password': password + } + try: + response = requests.post(str(self.config.get('url') + + "/users/login"), + data=credentials, + verify=self.verify, + headers=headers, + **self.extra_args) + except requests.RequestException as ex: + raise ApiError('error during login: %s' % str(ex)) + else: + raise CredentialsError('invalid token') + + if response.status_code == 401: + error_message = None + try: + # For 401: ACCEPT_LICENSE_REQUIRED, a detailed message is + # present in the response and passed to the exception. + error_message = response.json()['error']['message'] + except Exception: # pylint: disable=broad-except + pass + + if error_message: + raise CredentialsError('error during login: %s' % error_message) + else: + raise CredentialsError('invalid token') + try: + response.raise_for_status() + self.data_credentials = response.json() + except (requests.HTTPError, ValueError) as ex: + raise ApiError('error during login: %s' % str(ex)) + + if self.get_token() is None: + raise CredentialsError('invalid token') + + def get_token(self): + """Return the Authenticated Token to connect with QX Platform.""" + return self.data_credentials.get('id', None) + + def get_user_id(self): + """Return the user id in QX platform.""" + return self.data_credentials.get('userId', None) + + def get_config(self): + """Return the configuration that was set for this Credentials.""" + return self.config + + def set_token(self, access_token): + """Set the Access Token to connect with QX Platform API.""" + self.data_credentials['id'] = access_token + + def set_user_id(self, user_id): + """Set the user id to connect with QX Platform API.""" + self.data_credentials['userId'] = user_id + + +class Request: + """Request class that performs the HTTP calls. + + Note: + Set the proxy information, if present, from the configuration, + with the following format:: + + config = { + 'proxies': { + # If using 'urls', assume basic auth or no auth. + 'urls': { + 'http': 'http://user:password@1.2.3.4:5678', + 'https': 'http://user:password@1.2.3.4:5678', + } + # If using 'ntlm', assume NTLM authentication. + 'username_ntlm': 'domain\\username', + 'password_ntlm': 'password' + } + } + """ + + def __init__(self, token, config=None, verify=True, retries=5, + timeout_interval=1.0): + self.verify = verify + self.client_application = CLIENT_APPLICATION + self.config = config + self.errors_not_retry = [401, 403, 413] + + # Set the basic proxy settings, if present. + self.proxy_urls = None + self.ntlm_credentials = None + if config and 'proxies' in config: + if 'urls' in config['proxies']: + self.proxy_urls = self.config['proxies']['urls'] + if 'username_ntlm' and 'password_ntlm' in config['proxies']: + self.ntlm_credentials = { + 'username': self.config['proxies']['username_ntlm'], + 'password': self.config['proxies']['password_ntlm'] + } + + # Set the extra arguments to requests (proxy and auth). + self.extra_args = {} + if self.proxy_urls: + self.extra_args['proxies'] = self.proxy_urls + if self.ntlm_credentials: + self.extra_args['auth'] = HttpNtlmAuth( + self.ntlm_credentials['username'], + self.ntlm_credentials['password']) + + if self.config and ("client_application" in self.config): + self.client_application += ':' + self.config["client_application"] + self.credential = Credentials(token, self.config, verify, + proxy_urls=self.proxy_urls, + ntlm_credentials=self.ntlm_credentials) + + if not isinstance(retries, int): + raise TypeError('post retries must be positive integer') + self.retries = retries + self.timeout_interval = timeout_interval + self.result = None + self._max_qubit_error_re = re.compile( + r".*registers exceed the number of qubits, " + r"it can\'t be greater than (\d+).*") + + def check_token(self, response): + """Check is the user's token is valid.""" + if response.status_code == 401: + self.credential.obtain_token(config=self.config) + return False + return True + + def post(self, path, params='', data=None): + """POST Method Wrapper of the REST API.""" + self.result = None + data = data or {} + headers = {'Content-Type': 'application/json', + 'x-qx-client-application': self.client_application} + url = str(self.credential.config['url'] + path + '?access_token=' + + self.credential.get_token() + params) + retries = self.retries + while retries > 0: + response = requests.post(url, data=data, headers=headers, + verify=self.verify, **self.extra_args) + if not self.check_token(response): + response = requests.post(url, data=data, headers=headers, + verify=self.verify, + **self.extra_args) + + if self._response_good(response): + if self.result: + return self.result + elif retries < 2: + return response.json() + else: + retries -= 1 + else: + retries -= 1 + time.sleep(self.timeout_interval) + + # timed out + raise ApiError(usr_msg='Failed to get proper ' + + 'response from backend.') + + def put(self, path, params='', data=None): + """PUT Method Wrapper of the REST API.""" + self.result = None + data = data or {} + headers = {'Content-Type': 'application/json', + 'x-qx-client-application': self.client_application} + url = str(self.credential.config['url'] + path + '?access_token=' + + self.credential.get_token() + params) + retries = self.retries + while retries > 0: + response = requests.put(url, data=data, headers=headers, + verify=self.verify, **self.extra_args) + if not self.check_token(response): + response = requests.put(url, data=data, headers=headers, + verify=self.verify, + **self.extra_args) + if self._response_good(response): + if self.result: + return self.result + elif retries < 2: + return response.json() + else: + retries -= 1 + else: + retries -= 1 + time.sleep(self.timeout_interval) + # timed out + raise ApiError(usr_msg='Failed to get proper ' + + 'response from backend.') + + def get(self, path, params='', with_token=True): + """GET Method Wrapper of the REST API.""" + self.result = None + access_token = '' + if with_token: + access_token = self.credential.get_token() or '' + if access_token: + access_token = '?access_token=' + str(access_token) + url = self.credential.config['url'] + path + access_token + params + retries = self.retries + headers = {'x-qx-client-application': self.client_application} + while retries > 0: # Repeat until no error + response = requests.get(url, verify=self.verify, headers=headers, + **self.extra_args) + if not self.check_token(response): + response = requests.get(url, verify=self.verify, + headers=headers, **self.extra_args) + if self._response_good(response): + if self.result: + return self.result + elif retries < 2: + return response.json() + else: + retries -= 1 + else: + retries -= 1 + time.sleep(self.timeout_interval) + # timed out + raise ApiError(usr_msg='Failed to get proper ' + + 'response from backend.') + + def _sanitize_url(self, url): + """Strip any tokens or actual paths from url. + + Args: + url (str): The url to sanitize + + Returns: + str: The sanitized url + """ + return parse.urlparse(url).path + + def _response_good(self, response): + """check response. + + Args: + response (requests.Response): HTTP response. + + Returns: + bool: True if the response is good, else False. + + Raises: + ApiError: response isn't formatted properly. + """ + + url = self._sanitize_url(response.url) + + if response.status_code != requests.codes.ok: + logger.warning('Got a %s code response to %s: %s', + response.status_code, + url, + response.text) + if response.status_code in self.errors_not_retry: + raise ApiError(usr_msg='Got a {} code response to {}: {}'.format( + response.status_code, + url, + response.text)) + else: + return self._parse_response(response) + try: + if str(response.headers['content-type']).startswith("text/html;"): + self.result = response.text + return True + else: + self.result = response.json() + except (json.JSONDecodeError, ValueError): + usr_msg = 'device server returned unexpected http response' + dev_msg = usr_msg + ': ' + response.text + raise ApiError(usr_msg=usr_msg, dev_msg=dev_msg) + if not isinstance(self.result, (list, dict)): + msg = ('JSON not a list or dict: url: {0},' + 'status: {1}, reason: {2}, text: {3}') + raise ApiError( + usr_msg=msg.format(url, + response.status_code, + response.reason, response.text)) + if ('error' not in self.result or + ('status' not in self.result['error'] or + self.result['error']['status'] != 400)): + return True + + logger.warning("Got a 400 code JSON response to %s", url) + return False + + def _parse_response(self, response): + """parse text of response for HTTP errors. + + This parses the text of the response to decide whether to + retry request or raise exception. At the moment this only + detects an exception condition. + + Args: + response (Response): requests.Response object + + Returns: + bool: False if the request should be retried, True + if not. + + Raises: + RegisterSizeError: if invalid device register size. + """ + # convert error messages into exceptions + mobj = self._max_qubit_error_re.match(response.text) + if mobj: + raise RegisterSizeError( + 'device register size must be <= {}'.format(mobj.group(1))) + return True From 60c9a49f491e7a9046e61c6d63ddce89506ca975 Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Fri, 11 Jan 2019 16:07:09 +0100 Subject: [PATCH 3/3] Remove main from tests --- test/ibmq/test_ibmq_connector.py | 4 ---- test/ibmq/test_ibmq_integration.py | 4 ---- test/ibmq/test_ibmq_job.py | 4 ---- test/ibmq/test_ibmq_job_states.py | 4 ---- test/ibmq/test_ibmq_qobj.py | 4 ---- test/ibmq/test_reordering.py | 4 ---- 6 files changed, 24 deletions(-) diff --git a/test/ibmq/test_ibmq_connector.py b/test/ibmq/test_ibmq_connector.py index 35146cc21..382e58cf1 100644 --- a/test/ibmq/test_ibmq_connector.py +++ b/test/ibmq/test_ibmq_connector.py @@ -195,7 +195,3 @@ def test_url_unreachable(self, qe_token, qe_url): measure q[0] -> c[0]; measure q[44] -> c[44]; """ - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/test/ibmq/test_ibmq_integration.py b/test/ibmq/test_ibmq_integration.py index 060fb161b..7a8e0fd0d 100644 --- a/test/ibmq/test_ibmq_integration.py +++ b/test/ibmq/test_ibmq_integration.py @@ -146,7 +146,3 @@ def test_execute_two_remote(self, qe_token, qe_url): job = execute([qc, qc_extra], backend, seed=TestCompiler.seed) results = job.result() self.assertIsInstance(results, Result) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/test/ibmq/test_ibmq_job.py b/test/ibmq/test_ibmq_job.py index b6934f1ac..8e3a29a89 100644 --- a/test/ibmq/test_ibmq_job.py +++ b/test/ibmq/test_ibmq_job.py @@ -382,7 +382,3 @@ def _bell_circuit(): qc.cx(qr[0], qr[1]) qc.measure(qr, cr) return qc - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/test/ibmq/test_ibmq_job_states.py b/test/ibmq/test_ibmq_job_states.py index 6e056e3ef..a15da238f 100644 --- a/test/ibmq/test_ibmq_job_states.py +++ b/test/ibmq/test_ibmq_job_states.py @@ -550,7 +550,3 @@ class QObjResultAPI(BaseFakeAPI): } } ] - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/test/ibmq/test_ibmq_qobj.py b/test/ibmq/test_ibmq_qobj.py index 7aa1f776d..f8c554bae 100644 --- a/test/ibmq/test_ibmq_qobj.py +++ b/test/ibmq/test_ibmq_qobj.py @@ -227,7 +227,3 @@ def test_atlantic_circuit(self): result_local = self._local_backend.run(qobj).result() self.assertDictAlmostEqual(result_remote.get_counts(circuit), result_local.get_counts(circuit), delta=50) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/test/ibmq/test_reordering.py b/test/ibmq/test_reordering.py index 01735015c..b348d2cbb 100644 --- a/test/ibmq/test_reordering.py +++ b/test/ibmq/test_reordering.py @@ -96,7 +96,3 @@ def _get_backends(self, qe_token, qe_url): real_backend = None return sim_backend, real_backend - - -if __name__ == '__main__': - unittest.main(verbosity=2)