From abc0411d2600ebc10127f91a1d3627acfee11713 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Mon, 9 Jul 2018 13:34:21 +0200 Subject: [PATCH 1/6] Improve authentication handling Squash of the previous commits by @nonhermitian: lint fixes Make register token a kwarg - Allows to pass **dict to register. Add load_local_credentials - Load from qiskitrc, env vars, and local qconfig.py lint fixes lint fixes again get rid of circular imports style fixes get rid of cyclic import credentials mofifiers - Added get. store, and remove credentials functons to modify local qiskitrc file. New license format everything done in register now register called with no args looks for credentials Tests now read credentials from qiskitrc make auto register play nice with no internet connection better text for auto register fail Update readme update install docs fix skip provider if already registered Test new decorator and simple test additional tests fix lint lint again fix bad test class name ignore unused argments flip decorators test register from qiskitrc pylint Update test_offline test_quantumprogram.test_offline Fails locally with this PR if the user has credentials registered as the register function will just skip the fake registration as the IBMQProvider is already registered. Thus I made the test CI only. ouput warning when attempting to double registering provider Add provider name and unregister function auto-reg with user supplied names default provider naming for IBMQ in store_credentials register single provider by name go back to old ci check minor syntax change baseprovider names base check connection in _register to exit gracefully. remove auto-register and cleanup add get_credentials test remove default Qconfig try to fix online test Better error messages when registering fix lint Updates from Ali - Add available_providers function - Rename 'local' to 'core' provider. - Make sure you cannot unregister 'core' -Remove old docs about auto_register. save_credentials when calling register. Do Diego updates fix issues with refactor Updates - Move _register to its own file to avoid circular imports. fix provider name issue comment out offending test move _DEFAULT_PROVIDER to _register Fix specific provider registration simplify tests that need clean provider list typo Remove the ability to set provider name - Also set core -> terra changes based on comments remove provider name call load test credentials from qnet --- Qconfig.py.default | 23 -- README.md | 37 +-- bernstein_vazirani.ipynb | 211 ++++++++++++++ doc/install.rst | 77 ++++- qiskit/wrapper/_register.py | 58 ++++ qiskit/wrapper/configrc/__init__.py | 6 + qiskit/wrapper/configrc/_configrc.py | 405 +++++++++++++++++++++++++++ qiskit/wrapper/configrc/_settings.py | 13 + test/python/test_registration.py | 118 ++++++++ 9 files changed, 889 insertions(+), 59 deletions(-) delete mode 100644 Qconfig.py.default create mode 100644 bernstein_vazirani.ipynb create mode 100644 qiskit/wrapper/_register.py create mode 100644 qiskit/wrapper/configrc/__init__.py create mode 100644 qiskit/wrapper/configrc/_configrc.py create mode 100644 qiskit/wrapper/configrc/_settings.py create mode 100644 test/python/test_registration.py diff --git a/Qconfig.py.default b/Qconfig.py.default deleted file mode 100644 index 8373c8300bad..000000000000 --- a/Qconfig.py.default +++ /dev/null @@ -1,23 +0,0 @@ -# Before you can use the jobs API, you need to set up an access token. -# Log in to the IBM Q experience. Under "Account", generate a personal -# access token. Replace 'PUT_YOUR_API_TOKEN_HERE' below with the quoted -# token string. Uncomment the APItoken variable, and you will be ready to go. - -#APItoken = 'PUT_YOUR_API_TOKEN_HERE' - -config = { - 'url': 'https://quantumexperience.ng.bluemix.net/api', - - # If you have access to IBM Q features, you also need to fill the "hub", - # "group", and "project" details. Replace "None" on the lines below - # with your details from Quantum Experience, quoting the strings, for - # example: 'hub': 'my_hub' - # You will also need to update the 'url' above, pointing it to your custom - # URL for IBM Q. - 'hub': None, - 'group': None, - 'project': None -} - -if 'APItoken' not in locals(): - raise Exception('Please set up your access token. See Qconfig.py.') diff --git a/README.md b/README.md index c6361da3183f..100985cc76f1 100644 --- a/README.md +++ b/README.md @@ -126,43 +126,32 @@ your IBM Q Experience account: #### Configure your API token and QX credentials - 1. Create an _[IBM Q Experience](https://quantumexperience.ng.bluemix.net) > Account_ if you haven't already done so. + 2. Get an API token from the IBM Q Experience website under _My Account > Advanced > API Token_. This API token allows you to execute your programs with the IBM Q Experience backends. See: [Example](doc/example_real_backend.rst). -3. We are going to create a new file called `Qconfig.py` and insert the API token into it. This file must have these contents: + +3. We are now going to add the necessary credentials to QISKit. Take your token from step 2, here called `MY_API_TOKEN`, and pass it to the `store_credentials` function: ```python - APItoken = 'MY_API_TOKEN' - - config = { - 'url': 'https://quantumexperience.ng.bluemix.net/api', - # The following should only be needed for IBM Q Network users. - 'hub': 'MY_HUB', - 'group': 'MY_GROUP', - 'project': 'MY_PROJECT' - } - ``` + from qiskit import store_credentials -4. Substitute `MY_API_TOKEN` with your real API Token extracted in step 2. + store_credentials(`MY_API_TOKEN`) + ``` -5. If you have access to the IBM Q Network features, you also need to setup the - values for your hub, group, and project. You can do so by filling the - `config` variable with the values you can find on your IBM Q account - page. +4. If you have access to the IBM Q Network features, you also need to pass the + values for your url, hub, group, and project found on your IBM Q account + page to `store_credentials`. -Once the `Qconfig.py` file is set up, you have to move it under the same directory/folder where your program/tutorial resides, so it can be imported and be used to authenticate with the `register()` function. For example: +Once the credentials are stored, you can register them via ```python from qiskit import register -import Qconfig -register(Qconfig.APItoken, Qconfig.config["url"], - hub=Qconfig.config["hub"], - group=Qconfig.config["group"], - project=Qconfig.config["project"]) +register() ``` -For more details on this and more information see +For more details on this installation method, using environmental variables, +and the old `Qconfig.py` method, see [our Qiskit documentation](https://www.qiskit.org/documentation/). diff --git a/bernstein_vazirani.ipynb b/bernstein_vazirani.ipynb new file mode 100644 index 000000000000..1c2f3e43fbba --- /dev/null +++ b/bernstein_vazirani.ipynb @@ -0,0 +1,211 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Note: Trusted Notebook\" width=\"500 px\" align=\"left\">" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## _*The Bernstein-Vazirani Algorithm*_ \n", + "\n", + "In this tutorial, we introduce the [Bernstein-Vazirani algorithm](http://epubs.siam.org/doi/abs/10.1137/S0097539796300921), which is one of the earliest algorithms demonstrating the power of quantum computing. Despite its simplicity, it is often used and is the inspiration for many other quantum algorithms even today; it is the basis of the power of the short-depth quantum circuits, as in [Bravyi et al.](https://arxiv.org/abs/1704.00690) that uses its non-oracular version, or in [Linke et al.](http://www.pnas.org/content/114/13/3305.full) that uses it to test the performance of the quantum processors (see also the [talk by Ken Brown](https://www.youtube.com/watch?v=eHV9LTiePrQ) at the ThinkQ 2017 conference). Here, we show the implementation of the Bernstein-Vazirani algorithm **without using entanglement** based on [Du et al.](https://arxiv.org/abs/quant-ph/0012114) on QISKit and test it on IBM Q systems. \n", + "\n", + "The latest version of this notebook is available on https://github.com/QISKit/qiskit-tutorial.\n", + "\n", + "***\n", + "### Contributors\n", + "Rudy Raymond" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction \n", + "\n", + "The Bernstein-Vazirani algorithm deals with finding a hidden integer $a \\in \\{0,1\\}^n$ from an oracle $f_a$ that returns a bit $a \\cdot x \\equiv \\sum_i a_i x_i \\mod 2$ upon receiving an input $x \\in \\{0,1\\}^n$. A classical oracle returns $f_a(x) = a \\cdot x \\mod 2$ given an input $x$. Meanwhile, a quantum oracle behaves similarly but can be queried with superposition of input $x$'s. \n", + "\n", + "Classically, the hidden integer $a$ can be revealed by querying the oracle with $x = 1, 2, \\ldots, 2^i, \\ldots, 2^{n-1}$, where each query reveals the $i$-th bit of $a$ (or, $a_i$). For example, with $x=1$ one can obtain the least significant bit of $a$, and so on. This turns out to be an optimal strategy; any classical algorithm that finds the hidden integer with high probability must query the oracle $\\Omega(n)$ times. However, given a corresponding quantum oracle, the hidden integer can be found with only $1$ query using the Bernstein-Vazirani algorithm. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The Algorithm\n", + "\n", + "The Bernstein-Vazirani algorithm to find the hidden integer is very simple: start from a $|0\\rangle$ state, apply Hadamard gates, query the oracle, apply Hadamard gates, and measure. The correctness of the algorithm is best explained by looking at the transformation of a quantum register $|a \\rangle$ by $n$ Hadamard gates, each applied to the qubit of the register. It can be shown that\n", + "\n", + "$$\n", + "|a\\rangle \\xrightarrow{H^{\\otimes n}} \\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} (-1)^{a\\cdot x}|x\\rangle.\n", + "$$\n", + "\n", + "In particular, when we start with a quantum register $|0\\rangle$ and apply $n$ Hadamard gates to it, we have the familiar quantum superposition as below\n", + "\n", + "$$\n", + "|0\\rangle \\xrightarrow{H^{\\otimes n}} \\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} |x\\rangle,\n", + "$$\n", + "\n", + "which is slightly different from the Hadamard transform of the reqister $|a \\rangle$ by the phase $(-1)^{a\\cdot x}$. \n", + "\n", + "Now, the quantum oracle $f_a$ returns $1$ on input $x$ such that $a \\cdot x \\equiv 1 \\mod 2$, and returns $0$ otherwise. This means we have the following transformation:\n", + "\n", + "$$\n", + "|x \\rangle \\left(|0\\rangle - |1\\rangle \\right) \\xrightarrow{f_a} | x \\rangle \\left(|0 \\oplus f_a(x) \\rangle - |1 \\oplus f_a(x) \\rangle \\right) = (-1)^{a\\cdot x} |x \\rangle \\left(|0\\rangle - |1\\rangle \\right). \n", + "$$\n", + "\n", + "Notice that the second register $|0\\rangle - |1\\rangle$ in the above does not change and can be omitted for simplicity. In short, the oracle can be used to create $(-1)^{a\\cdot x}|x\\rangle$ from the input $|x \\rangle$. In this tutorial, we follow [Du et al.](https://arxiv.org/abs/quant-ph/0012114) to generate a circuit for a quantum oracle without the need of an ancilla qubit (often used in the standard quantum oracle). \n", + "\n", + "The algorithm to reveal the hidden integer follows naturally by querying the quantum oracle $f_a$ with the quantum superposition obtained from the Hadamard transformation of $|0\\rangle$. Namely,\n", + "\n", + "$$\n", + "|0\\rangle \\xrightarrow{H^{\\otimes n}} \\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} |x\\rangle \\xrightarrow{f_a} \\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} (-1)^{a\\cdot x}|x\\rangle.\n", + "$$\n", + "\n", + "Because the inverse of the $n$ Hadamard gates is again the $n$ Hadamard gates, we can obtain $a$ by\n", + "\n", + "$$\n", + "\\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} (-1)^{a\\cdot x}|x\\rangle \\xrightarrow{H^{\\otimes n}} |a\\rangle.\n", + "$$\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The (Inner-Product) Oracle \n", + "\n", + "Here, we describe how to build the oracle used in the Bernstein-Vazirani algorithm. The oracle is also referred to as the [inner-product oracle](https://arxiv.org/pdf/quant-ph/0108095.pdf) (while the oracle of the Grover search is known as the Equivalence, or EQ, oracle). Notice that it transforms $|x\\rangle$ into $(-1)^{a\\cdot x} |x\\rangle$. Clearly, we can observe that\n", + "\n", + "$$\n", + "(-1)^{a\\cdot x} = (-1)^{a_1 x_1} \\ldots (-1)^{a_ix_i} \\ldots (-1)^{a_nx_n} = \\prod_{i: a_i = 1} (-1)^{x_i}. \n", + "$$\n", + "\n", + "Therefore, the inner-product oracle can be realized by the following unitary transformation, which is decomposable as single-qubit unitaries: \n", + "\n", + "$$\n", + "O_{f_a} = O^1 \\otimes O^2 \\otimes \\ldots \\otimes O^i \\otimes \\ldots \\otimes O^n, \n", + "$$\n", + "where $O^i = (1 - a_i)I + a_i Z$, where $Z$ is the Pauli $Z$ matrix and $I$ is the identity matrix for $a_i \\in \\{0,1\\}$. \n", + "\n", + "Notice that we start from a separable quantum state $|0\\rangle$ and apply a series of transformations that are separable (i.e., can be described by unitaries acting on a single qubit): Hadamard gates to each qubit, followed by the call to the *decomposable* quantum oracle as [Du et al.](https://arxiv.org/abs/quant-ph/0012114), and another Hadamard gate. Hence, there is no entanglement created during the computation. This is in contrast with the circuit at [Linke et al.](http://www.pnas.org/content/114/13/3305.full) that used CNOT gates to realize the oracle and an ancilla qubit to store the answer of the oracle. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The Circuit \n", + "\n", + "We now implement the Bernstein-Vazirani algorithm with QISKit by first preparing the environment." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2018-06-21T10:46:58.578540Z", + "start_time": "2018-06-21T10:46:55.706847Z" + } + }, + "outputs": [], + "source": [ + "from qiskit import *\n", + "register()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We first set the number of qubits used in the experiment, and the hidden integer $a$ to be found by the Bernstein-Vazirani algorithm. The hidden integer $a$ determines the circuit for the quantum oracle. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We then use QISKit to program the Bernstein-Vazirani algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2018-06-21T10:53:04.261863Z", + "start_time": "2018-06-21T10:53:02.374975Z" + } + }, + "outputs": [], + "source": [ + "nQubits = 16 # number of physical qubits\n", + "a = 101 # integer whose bitstring is 1100101\n", + "\n", + "qr = QuantumRegister(nQubits)\n", + "cr = ClassicalRegister(nQubits)\n", + "bvCircuit = QuantumCircuit(qr, cr)\n", + "\n", + "# Apply Hadamard gates before querying the oracle\n", + "for i in range(nQubits): bvCircuit.h(qr[i])\n", + " \n", + "# Apply barrier so that it is not optimized by the compiler\n", + "bvCircuit.barrier()\n", + "\n", + "# Apply the inner-product oracle\n", + "for i in range(nQubits):\n", + " if (a & (1 << i)): bvCircuit.z(qr[i]) \n", + " else: bvCircuit.iden(qr[i])\n", + " \n", + "# Apply barrier \n", + "bvCircuit.barrier()\n", + "\n", + "#Apply Hadamard gates after querying the oracle\n", + "for i in range(nQubits): bvCircuit.h(qr[i])\n", + " \n", + "# Measurement\n", + "for i in range(nQubits): bvCircuit.measure(qr[i], cr[i])\n", + " \n", + "backend = \"local_qasm_simulator\"\n", + "#backend = 'ibmqx5'\n", + "results = execute(bvCircuit, backend=backend).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Experiment with Simulators\n", + "\n", + "We can run the above circuit on the simulator. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/install.rst b/doc/install.rst index 0f667a7a0217..4d7a8c203938 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -41,19 +41,64 @@ This will install the latest stable release along with all the dependencies. you haven't already done so - Get an API token from the IBM Q experience website under “My Account” > “Personal Access Token” -- The API token needs to be placed in a file called ``Qconfig.py``. For - convenience, we provide a default version of this file that you - can use as a reference: `Qconfig.py.default`_. After downloading that - file, copy it into the folder where you will be invoking the SDK (on - Windows, replace ``cp`` with ``copy``): + +3.1 Store API credentials locally +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For most users, storing your API credentials is most easily done locally. +Your information is stored locally in a configuration file called `qiskitrc`. +To store your information, simply run: + +.. code:: python + + from qiskit import store_credentials + + store_credentials(`MY_API_TOKEN`) + +where `MY_API_TOKEN` should be replaced with your token. + +If you are on the IBM Q network, you must also pass `url`, +`hub`, `group`, and `project` arguments to `store_credentials`. + +To register your credentials with QISKit, simply run: + +.. code:: python + + from qiskit import register + + register() + + +3.2 Load API credentials from environment variables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For more advanced users, it is possible to load API credentials from +environmental variables. Specifically, one may set `QE_TOKEN`, +`QE_URL`, `QE_HUB`, `QE_GROUP`, and `QE_PROJECT`. These can then be registered +with QISKit: + +.. code:: python + + from qiskit import register + + register() + +3.3 Load API credentials from Qconfig.py +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The API token can be loaded from a file called +``Qconfig.py``. For convenience, we provide a default version of +this file that you can use as a reference: `Qconfig.py.default`_. +After downloading that file, copy it into the folder where you +will be invoking the SDK (on Windows, replace ``cp`` with ``copy``): .. code:: sh cp Qconfig.py.default Qconfig.py -- Open your ``Qconfig.py``, remove the ``#`` from the beginning of the API - token line, and copy/paste your API token into the space between the - quotation marks on that line. Save and close the file. +Open your ``Qconfig.py``, remove the ``#`` from the beginning of the API +token line, and copy/paste your API token into the space between the +quotation marks on that line. Save and close the file. For example, a valid and fully configured ``Qconfig.py`` file would look like: @@ -65,10 +110,10 @@ For example, a valid and fully configured ``Qconfig.py`` file would look like: 'url': 'https://quantumexperience.ng.bluemix.net/api' } -- If you have access to the IBM Q features, you also need to setup the - values for your hub, group, and project. You can do so by filling the - ``config`` variable with the values you can find on your IBM Q account - page. +If you have access to the IBM Q features, you also need to setup the +values for your hub, group, and project. You can do so by filling the +``config`` variable with the values you can find on your IBM Q account +page. For example, a valid and fully configured ``Qconfig.py`` file for IBM Q users would look like: @@ -85,6 +130,14 @@ users would look like: 'project': 'MY_PROJECT' } +If the `Qconfig.py` is in the current working directory, then it can be +automatrically registered with QISKit: + +.. code:: python + + from qiskit import register + + register() Install Jupyter-based tutorials =============================== diff --git a/qiskit/wrapper/_register.py b/qiskit/wrapper/_register.py new file mode 100644 index 000000000000..00d100d0cb66 --- /dev/null +++ b/qiskit/wrapper/_register.py @@ -0,0 +1,58 @@ +# -*- 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. + +"""Internal register function""" + +import warnings +from qiskit import QISKitError +from qiskit.backends.ibmq.ibmqprovider import IBMQProvider +from qiskit.wrapper.defaultqiskitprovider import DefaultQISKitProvider +from qiskit.wrapper.configrc import _settings as rc_set + +# Default provider used by the rest of the functions on this module. Please +# note that this is a global object. +_DEFAULT_PROVIDER = DefaultQISKitProvider() + + +def _register(token=None, url='https://quantumexperience.ng.bluemix.net/api', + hub=None, group=None, project=None, proxies=None, verify=True): + """ + Internal function that calls the actual providers. + Args: + token (str): The token used to register on the online backend such + as the quantum experience. + url (str): The url used for online backend such as the quantum + experience. + hub (str): The hub used for online backend. + group (str): The group used for online backend. + project (str): The project used for online backend. + proxies (dict): Proxy configuration for the API, as a dict with + 'urls' and credential keys. + verify (bool): If False, ignores SSL certificates errors. + Raises: + QISKitError: if the provider name is not recognized. + """ + _registered_names = [p.name for p in _DEFAULT_PROVIDER.providers] + if 'quantumexperience' in url: + provider_name = 'ibmq' + elif 'q-console' in url: + provider_name = 'qnet' + else: + raise QISKitError('Unkown provider name.') + if provider_name not in _registered_names: + try: + provider = IBMQProvider(token, url, + hub, group, project, + proxies, verify) + _DEFAULT_PROVIDER.add_provider(provider) + except ConnectionError: + warnings.warn('%s not registered. No connection established.' % provider_name) + else: + if rc_set.REGISTER_CALLED: + warnings.warn( + "%s already registered. Use unregister('%s') to remove." % ( + provider_name, provider_name)) diff --git a/qiskit/wrapper/configrc/__init__.py b/qiskit/wrapper/configrc/__init__.py new file mode 100644 index 000000000000..7c6d72e116e0 --- /dev/null +++ b/qiskit/wrapper/configrc/__init__.py @@ -0,0 +1,6 @@ +# -*- 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. diff --git a/qiskit/wrapper/configrc/_configrc.py b/qiskit/wrapper/configrc/_configrc.py new file mode 100644 index 000000000000..20e5796db05d --- /dev/null +++ b/qiskit/wrapper/configrc/_configrc.py @@ -0,0 +1,405 @@ +# -*- 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. + +""" +A module for generating and manipulating the qiskitrc file. +""" +import os +import ast +import importlib.util +import configparser +from qiskit import QISKitError +from qiskit.wrapper._register import _register +from qiskit.wrapper.configrc import _settings as rc_set + + +def has_qiskit_configrc(): + """ + Checks to see if the qikitrc file exists in the default + location, i.e. HOME/.qiskit/qiskitrc + + Returns: + bool: True if file exists, False otherwise. + """ + has_rc = False + qiskit_conf_dir = os.path.join(os.path.expanduser("~"), '.qiskit') + if os.path.exists(qiskit_conf_dir): + qiskit_rc_file = os.path.join(qiskit_conf_dir, 'qiskitrc') + qrc_exists = os.path.isfile(qiskit_rc_file) + if qrc_exists: + has_rc = True + rc_set.QISKIT_RC_FILE = qiskit_rc_file + return has_rc + + +def generate_qiskitrc(overwrite=False): + """ + Generate a blank qiskitrc file. + + Args: + overwrite (bool): Overwrite existing file. Default is False. + + Returns: + bool: True if file was written, False otherwise. + + Raises: + QISKitError: + Could not write to user home directory. + """ + # Check for write access to home dir + if not os.access(os.path.expanduser("~"), os.W_OK): + raise QISKitError('No write access to home directory.') + qiskit_conf_dir = os.path.join(os.path.expanduser("~"), '.qiskit') + if not os.path.exists(qiskit_conf_dir): + try: + os.mkdir(qiskit_conf_dir) + except Exception: + raise QISKitError( + 'Unable to write QISKit config file to home directory.') + rc_set.QISKIT_RC_FILE = os.path.join(qiskit_conf_dir, 'qiskitrc') + qrc_exists = os.path.isfile(rc_set.QISKIT_RC_FILE) + if qrc_exists: + if overwrite: + os.remove(rc_set.QISKIT_RC_FILE) + else: + return False + # Write a basic file with REGISTRATION section + cfgfile = open(rc_set.QISKIT_RC_FILE, 'w') + config = configparser.ConfigParser() + config.add_section('REGISTRATION') + config.write(cfgfile) + cfgfile.close() + return True + + +def has_qiskitrc_key(section, key): + """ + Checks if a given key is already in a given + section of the configrc. + + Args: + section (str): Section in which to look. + key (str): Key of interest. + + Returns: + bool: True if key exits, False otherwise. + + Raises: + QISKitError: + Config file not found. + """ + out = False + if rc_set.QISKIT_RC_FILE is None: + raise QISKitError('QISKit config file not found.') + config = configparser.ConfigParser() + config.read(rc_set.QISKIT_RC_FILE) + if config.has_section(section): + if key in config.options(section): + out = True + else: + raise QISKitError('Section %s not found in qiskitrc.' % section) + return out + + +def write_qiskitrc_key(section, key, value, overwrite=False): + """ + Writes a single key value to the qiskitrc file. + + Args: + section (str): Section in which to write. + key (str): Key to be written. + value (str): Value to be written + overwrite (bool): Overwrite key if it exists. Default is False. + + Raises: + QISKitError: + Config file not found. + """ + if rc_set.QISKIT_RC_FILE is None: + raise QISKitError('QISKit config file not found.') + config = configparser.ConfigParser() + config.read(rc_set.QISKIT_RC_FILE) + if not config.has_section(section): + config.add_section(section) + if has_qiskitrc_key(section, key) and (not overwrite): + raise QISKitError('%s is already present and overwrite=False' % key) + elif has_qiskitrc_key(section, key): + config.remove_option(section, key) + config.set(section, key, str(value)) + cfgfile = open(rc_set.QISKIT_RC_FILE, 'w') + config.write(cfgfile) + cfgfile.close() + + +def read_qiskitrc_key(section, key): + """ + Reads a single key value from the qiskitrc file. + + Parameters: + section (str): The section to search. + key (str): The key whose value we want. + + Returns: + value: Value contained in section:key. + + Raises: + QISKitError: + Config file not found. + """ + if not has_qiskit_configrc(): + raise QISKitError('QISKit config file not found.') + config = configparser.ConfigParser() + config.read(rc_set.QISKIT_RC_FILE) + if not config.has_section(section): + raise QISKitError('Section %s is missing from qiskitrc.' % section) + if key not in config.options(section): + raise QISKitError('Key %s is missing in section %s in qiskitrc.' % (key, section)) + # Need to do an eval here in the case value is a dict. + out = ast.literal_eval(config.get(section, key)) + return out + + +def store_credentials(token=None, + url='https://quantumexperience.ng.bluemix.net/api', + hub=None, group=None, project=None, proxies=None, + verify=True, overwrite=False): + """Store provider credentials in local qiskitrc. + + Args: + token (str): The token used to register on the online backend such + as the quantum experience. + url (str): The url used for online backend such as the quantum + experience. + hub (str): The hub used for online backend. + group (str): The group used for online backend. + project (str): The project used for online backend. + proxies (dict): Proxy configuration for the API, as a dict with + 'urls' and credential keys. + verify (bool): If False, ignores SSL certificates errors. + overwrite (bool): Overwrite existing credentials, default is False. + Raises: + QISKitError: If provider already exists and overwrite=False. + """ + if 'quantumexperience' in url: + provider_name = 'ibmq' + elif 'q-console' in url: + provider_name = 'qnet' + else: + raise QISKitError('Cannot parse provider name from credentials.') + pro_dict = {'token': token, 'url': url, + 'hub': hub, 'group': group, 'project': project, + 'proxies': proxies, 'verify': verify} + if not has_qiskit_configrc(): + generate_qiskitrc() + if has_qiskitrc_key('REGISTRATION', provider_name) and (not overwrite): + raise QISKitError('%s is already present and overwrite=False' + % provider_name) + write_qiskitrc_key('REGISTRATION', provider_name, + pro_dict, overwrite=True) + + +def get_credentials(provider_name=None): + """Get the provider credentials + stored in qiskitrc. + + If no provider_name given, returns list + of provider names in qiskitrc. + + Args: + provider_name (str): Name of provider. + + Returns: + list: List of providers in qiskitrc if + provider_name=None. + + Raises: + QISKitError: If missing section or qiskitrc file. + """ + if provider_name is not None: + return read_qiskitrc_key('REGISTRATION', provider_name) + else: # List all stored provider names + if has_qiskit_configrc(): + config = configparser.ConfigParser() + config.read(rc_set.QISKIT_RC_FILE) + section = 'REGISTRATION' + if not config.has_section(section): + raise QISKitError('Section %s is missing from qiskitrc.' % section) + else: + return config.options(section) + else: + raise QISKitError('qiskitrc file not found.') + + +def remove_credentials(provider_name): + """Remove provider credentials + from qiskitrc. + + Args: + provider_name (str): Name of provider. + + Raises: + QISKitError: If missing section and/or provider_name. + """ + if has_qiskit_configrc(): + config = configparser.ConfigParser() + config.read(rc_set.QISKIT_RC_FILE) + section = 'REGISTRATION' + if not config.has_section(section): + raise QISKitError( + 'Section %s is missing from qiskitrc.' % section) + else: + if provider_name not in config.options(section): + raise QISKitError( + 'Key %s is missing in section %s in qiskitrc.' + % (provider_name, section)) + else: + config.remove_option(section, provider_name) + cfgfile = open(rc_set.QISKIT_RC_FILE, 'w') + config.write(cfgfile) + cfgfile.close() + else: + raise QISKitError('qiskitrc file not found.') + + +def qiskitrc_register_providers(specific_provider=None): + """ Registers providers in qiskitrc + + Args: + specific_provider (str): Register only this provider, if any. + + Raises: + QISKitError: if the qiskitrc file cannot be read. + """ + if rc_set.QISKIT_RC_FILE is None: + raise QISKitError('QISKit config file not found.') + config = configparser.ConfigParser() + config.read(rc_set.QISKIT_RC_FILE) + if not config.has_section('REGISTRATION'): + raise QISKitError("Missing 'REGISTRATION' section in qiskitrc") + did_register = 0 + for pro in config.options('REGISTRATION'): + pro_dict = ast.literal_eval(config.get('REGISTRATION', pro)) + if specific_provider is None: + _register(**pro_dict) + did_register = 1 + elif pro == specific_provider: + _register(**pro_dict) + did_register = 1 + if not did_register and specific_provider is not None: + raise QISKitError('Provider %s credentials not found.' % specific_provider) + + +def get_qconfig_credentials(provider_name=None, + specific_provider=False): + """ + Looks for registration information in a Qconfig.py + file in the cwd. + + Args: + provider_name (str): Name of provider + specific_provider (bool): Is specific provider requested. + + Returns: + bool: Did registration occur. + + Raises: + QISKitError: Error loading Qconfig.py + """ + did_register = 0 + cwd = os.getcwd() + if os.path.isfile(cwd + '/Qconfig.py'): + try: + spec = importlib.util.spec_from_file_location( + "Qconfig", cwd+'/Qconfig.py') + q_config = importlib.util.module_from_spec(spec) + spec.loader.exec_module(q_config) + config_dict = q_config.config + config_dict['token'] = q_config.APItoken + if specific_provider: + if (provider_name == 'ibmq' and + 'quantumexperience' in config_dict['url']) \ + or (provider_name == 'qnet' and + 'q-console' in config_dict['url']): + _register(**config_dict) + + else: + if not has_qiskit_configrc() and \ + os.environ.get('QE_TOKEN') is None: + raise QISKitError( + 'Provider %s credentials not found.' % specific_provider) + + else: + _register(**config_dict) + did_register = 1 + except Exception: + raise QISKitError('Error loading Qconfig.py') + return did_register + + +def get_env_credentials(provider_name=None, + specific_provider=False): + """ + Looks for registration information in the environment variables. + + Args: + provider_name (str): Name of provider + specific_provider (bool): Is specific provider requested. + + Returns: + bool: Did registration occur. + + Raises: + QISKitError: Specific provider not found. + """ + did_register = 0 + token = os.environ.get('QE_TOKEN') + url = os.environ.get('QE_URL') + if token is not None: + # We have at least a token so lets load it + if url is None: + url = 'https://quantumexperience.ng.bluemix.net/api' + + if specific_provider: + if (provider_name == 'ibmq' and + 'quantumexperience' in url) \ + or (provider_name == 'qnet' and + 'q-console' in url): + _register(token=token, + url=url, + hub=os.environ.get('QE_HUB'), + group=os.environ.get('QE_GROUP'), + project=os.environ.get('QE_PROJECT')) + else: + if not has_qiskit_configrc(): + raise QISKitError( + 'Provider %s credentials not found.' % provider_name) + else: + _register(token=token, + url=url, + hub=os.environ.get('QE_HUB'), + group=os.environ.get('QE_GROUP'), + project=os.environ.get('QE_PROJECT')) + did_register = 1 + return did_register + + +def get_qiskitrc_credentials(provider_name=None): + """ + Looks for registration information in qiskitrc file. + + Args: + provider_name (str): Name of provider + + Returns: + bool: Did registration occur. + """ + # Look at qiksitrc for saved data + did_register = 0 + if has_qiskit_configrc(): + qiskitrc_register_providers(specific_provider=provider_name) + did_register = 1 + return did_register diff --git a/qiskit/wrapper/configrc/_settings.py b/qiskit/wrapper/configrc/_settings.py new file mode 100644 index 000000000000..5133c6efbd10 --- /dev/null +++ b/qiskit/wrapper/configrc/_settings.py @@ -0,0 +1,13 @@ +# -*- 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. + +"""Module that holds the runtime location of qiskitrc file. +""" + +QISKIT_RC_FILE = None + +REGISTER_CALLED = 0 diff --git a/test/python/test_registration.py b/test/python/test_registration.py new file mode 100644 index 000000000000..ec0048602d41 --- /dev/null +++ b/test/python/test_registration.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017, 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. + +# pylint: disable=invalid-name, unused-argument + +"""Tests for QISKit API registration""" +import os +import qiskit.wrapper._wrapper as wrap +from qiskit.wrapper.configrc._configrc import (has_qiskit_configrc, + generate_qiskitrc, + store_credentials, + get_credentials, + remove_credentials) +from .common import (QiskitTestCase, requires_ci, requires_qe_access) + + +class TestRegistration(QiskitTestCase): + """Tests for QISKit API registration""" + + @requires_ci + def test_amake_qiskitrc(self): + """Generated qiskitrc file.""" + self.assertTrue(not has_qiskit_configrc()) + generate_qiskitrc() + self.assertTrue(has_qiskit_configrc()) + + @requires_ci + def test_write_qiskitrc_data(self): + """Add provider info to qiskitc""" + generate_qiskitrc(overwrite=True) + self.assertTrue(len(get_credentials()) == 0) + store_credentials(token='abcdefg') + self.assertTrue(len(get_credentials()) == 1) + + @requires_ci + def test_get_qiskitrc_data(self): + """Get provider info from qiskitc""" + generate_qiskitrc(overwrite=True) + store_credentials(token='abcdefg') + _creds = get_credentials('ibmq') + self.assertTrue(_creds['token'] == 'abcdefg') + + @requires_ci + def test_remove_qiskitrc_data(self): + """Remove provider info from qiskitc""" + generate_qiskitrc(overwrite=True) + store_credentials(token='abcdefg') + self.assertTrue(len(get_credentials()) == 1) + remove_credentials('ibmq') + self.assertTrue(len(get_credentials()) == 0) + + @requires_ci + @requires_qe_access + def test_register_from_env(self, QE_TOKEN, QE_URL, + hub=None, group=None, project=None): + """Register from env vars""" + # Remove all providers execept core + pro = wrap.available_providers() + for p in pro: + if p != 'terra': + wrap.unregister(p) + # Make sure blank qiskitrc + generate_qiskitrc(overwrite=True) + orig_back = len(wrap.available_backends()) + wrap.register() + new_back = len(wrap.available_backends()) + self.assertTrue(new_back > orig_back) + + @requires_ci + @requires_qe_access + def test_register_from_qiskitrc(self, QE_TOKEN, QE_URL, + hub=None, group=None, project=None): + """Register from qiskitrc""" + # Remove all providers execept core + pro = wrap.available_providers() + for p in pro: + if p != 'terra': + wrap.unregister(p) + orig_back = len(wrap.available_backends()) + # Make sure blank qiskitrc + generate_qiskitrc(overwrite=True) + _token = os.getenv('QE_TOKEN') + # Remove env var so that it does not trigger + # env var registration + del os.environ['QE_TOKEN'] + store_credentials(token=QE_TOKEN, + url=QE_URL, + hub=hub, + group=group, + project=project) + wrap.register() + new_back = len(wrap.available_backends()) + self.assertTrue(new_back > orig_back) + # Put token back in env + os.environ['QE_TOKEN'] = _token + + @requires_ci + @requires_qe_access + def test_save_from_register(self, QE_TOKEN, QE_URL, + hub=None, group=None, project=None): + """Save credentials when calling register""" + # Remove all providers execept core + pro = wrap.available_providers() + for p in pro: + if p != 'terra': + wrap.unregister(p) + # Make sure blank qiskitrc + generate_qiskitrc(overwrite=True) + wrap.register(token=QE_TOKEN, + url=QE_URL, + hub=hub, group=group, project=project, + save_credentials=True) + _creds = get_credentials() + self.assertTrue(len(_creds) > 0) From 9d9a96aad82201f196cdc29b4a62f57122509513 Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Mon, 9 Jul 2018 14:34:21 +0200 Subject: [PATCH 2/6] Refactor credentials discovering in the wrapper Refactor the features for reading credentials from different sources, centered in the `_configrc.py` file: * rename the module to `wrapper.credentials` for generalizing. * make the default level of granularity a dict of providers when working with qiskitrc files. * simplify the usage by writing and reading dicts of providers directly. * replace the format used by the configuration file, for simplicity: each provider account is an .ini section, with the variables listed explicitely. Revise credentials discovery from qconfig * Move the discovery of credentials to its own file. * Revise the arguments and return value for unifying it with the rest of discovery features. * Add a deprecation warning when using the `Qconfig.py`. Revise credentials discovery from environment * Refactor and move the discovery of credentials from environment variables to conform to the rest of the module changes. * Further clean the remaining methods of `_configrc.py`, making it only contain the functionality related to the config file. --- bernstein_vazirani.ipynb | 211 --------- qiskit/wrapper/_register.py | 2 +- qiskit/wrapper/configrc/__init__.py | 6 - qiskit/wrapper/configrc/_configrc.py | 405 ------------------ qiskit/wrapper/credentials/__init__.py | 68 +++ qiskit/wrapper/credentials/_configrc.py | 144 +++++++ qiskit/wrapper/credentials/_environ.py | 44 ++ qiskit/wrapper/credentials/_qconfig.py | 55 +++ .../{configrc => credentials}/_settings.py | 2 - test/python/test_registration.py | 10 +- 10 files changed, 317 insertions(+), 630 deletions(-) delete mode 100644 bernstein_vazirani.ipynb delete mode 100644 qiskit/wrapper/configrc/__init__.py delete mode 100644 qiskit/wrapper/configrc/_configrc.py create mode 100644 qiskit/wrapper/credentials/__init__.py create mode 100644 qiskit/wrapper/credentials/_configrc.py create mode 100644 qiskit/wrapper/credentials/_environ.py create mode 100644 qiskit/wrapper/credentials/_qconfig.py rename qiskit/wrapper/{configrc => credentials}/_settings.py (92%) diff --git a/bernstein_vazirani.ipynb b/bernstein_vazirani.ipynb deleted file mode 100644 index 1c2f3e43fbba..000000000000 --- a/bernstein_vazirani.ipynb +++ /dev/null @@ -1,211 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Note: Trusted Notebook\" width=\"500 px\" align=\"left\">" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## _*The Bernstein-Vazirani Algorithm*_ \n", - "\n", - "In this tutorial, we introduce the [Bernstein-Vazirani algorithm](http://epubs.siam.org/doi/abs/10.1137/S0097539796300921), which is one of the earliest algorithms demonstrating the power of quantum computing. Despite its simplicity, it is often used and is the inspiration for many other quantum algorithms even today; it is the basis of the power of the short-depth quantum circuits, as in [Bravyi et al.](https://arxiv.org/abs/1704.00690) that uses its non-oracular version, or in [Linke et al.](http://www.pnas.org/content/114/13/3305.full) that uses it to test the performance of the quantum processors (see also the [talk by Ken Brown](https://www.youtube.com/watch?v=eHV9LTiePrQ) at the ThinkQ 2017 conference). Here, we show the implementation of the Bernstein-Vazirani algorithm **without using entanglement** based on [Du et al.](https://arxiv.org/abs/quant-ph/0012114) on QISKit and test it on IBM Q systems. \n", - "\n", - "The latest version of this notebook is available on https://github.com/QISKit/qiskit-tutorial.\n", - "\n", - "***\n", - "### Contributors\n", - "Rudy Raymond" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction \n", - "\n", - "The Bernstein-Vazirani algorithm deals with finding a hidden integer $a \\in \\{0,1\\}^n$ from an oracle $f_a$ that returns a bit $a \\cdot x \\equiv \\sum_i a_i x_i \\mod 2$ upon receiving an input $x \\in \\{0,1\\}^n$. A classical oracle returns $f_a(x) = a \\cdot x \\mod 2$ given an input $x$. Meanwhile, a quantum oracle behaves similarly but can be queried with superposition of input $x$'s. \n", - "\n", - "Classically, the hidden integer $a$ can be revealed by querying the oracle with $x = 1, 2, \\ldots, 2^i, \\ldots, 2^{n-1}$, where each query reveals the $i$-th bit of $a$ (or, $a_i$). For example, with $x=1$ one can obtain the least significant bit of $a$, and so on. This turns out to be an optimal strategy; any classical algorithm that finds the hidden integer with high probability must query the oracle $\\Omega(n)$ times. However, given a corresponding quantum oracle, the hidden integer can be found with only $1$ query using the Bernstein-Vazirani algorithm. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The Algorithm\n", - "\n", - "The Bernstein-Vazirani algorithm to find the hidden integer is very simple: start from a $|0\\rangle$ state, apply Hadamard gates, query the oracle, apply Hadamard gates, and measure. The correctness of the algorithm is best explained by looking at the transformation of a quantum register $|a \\rangle$ by $n$ Hadamard gates, each applied to the qubit of the register. It can be shown that\n", - "\n", - "$$\n", - "|a\\rangle \\xrightarrow{H^{\\otimes n}} \\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} (-1)^{a\\cdot x}|x\\rangle.\n", - "$$\n", - "\n", - "In particular, when we start with a quantum register $|0\\rangle$ and apply $n$ Hadamard gates to it, we have the familiar quantum superposition as below\n", - "\n", - "$$\n", - "|0\\rangle \\xrightarrow{H^{\\otimes n}} \\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} |x\\rangle,\n", - "$$\n", - "\n", - "which is slightly different from the Hadamard transform of the reqister $|a \\rangle$ by the phase $(-1)^{a\\cdot x}$. \n", - "\n", - "Now, the quantum oracle $f_a$ returns $1$ on input $x$ such that $a \\cdot x \\equiv 1 \\mod 2$, and returns $0$ otherwise. This means we have the following transformation:\n", - "\n", - "$$\n", - "|x \\rangle \\left(|0\\rangle - |1\\rangle \\right) \\xrightarrow{f_a} | x \\rangle \\left(|0 \\oplus f_a(x) \\rangle - |1 \\oplus f_a(x) \\rangle \\right) = (-1)^{a\\cdot x} |x \\rangle \\left(|0\\rangle - |1\\rangle \\right). \n", - "$$\n", - "\n", - "Notice that the second register $|0\\rangle - |1\\rangle$ in the above does not change and can be omitted for simplicity. In short, the oracle can be used to create $(-1)^{a\\cdot x}|x\\rangle$ from the input $|x \\rangle$. In this tutorial, we follow [Du et al.](https://arxiv.org/abs/quant-ph/0012114) to generate a circuit for a quantum oracle without the need of an ancilla qubit (often used in the standard quantum oracle). \n", - "\n", - "The algorithm to reveal the hidden integer follows naturally by querying the quantum oracle $f_a$ with the quantum superposition obtained from the Hadamard transformation of $|0\\rangle$. Namely,\n", - "\n", - "$$\n", - "|0\\rangle \\xrightarrow{H^{\\otimes n}} \\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} |x\\rangle \\xrightarrow{f_a} \\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} (-1)^{a\\cdot x}|x\\rangle.\n", - "$$\n", - "\n", - "Because the inverse of the $n$ Hadamard gates is again the $n$ Hadamard gates, we can obtain $a$ by\n", - "\n", - "$$\n", - "\\frac{1}{\\sqrt{2^n}} \\sum_{x\\in \\{0,1\\}^n} (-1)^{a\\cdot x}|x\\rangle \\xrightarrow{H^{\\otimes n}} |a\\rangle.\n", - "$$\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The (Inner-Product) Oracle \n", - "\n", - "Here, we describe how to build the oracle used in the Bernstein-Vazirani algorithm. The oracle is also referred to as the [inner-product oracle](https://arxiv.org/pdf/quant-ph/0108095.pdf) (while the oracle of the Grover search is known as the Equivalence, or EQ, oracle). Notice that it transforms $|x\\rangle$ into $(-1)^{a\\cdot x} |x\\rangle$. Clearly, we can observe that\n", - "\n", - "$$\n", - "(-1)^{a\\cdot x} = (-1)^{a_1 x_1} \\ldots (-1)^{a_ix_i} \\ldots (-1)^{a_nx_n} = \\prod_{i: a_i = 1} (-1)^{x_i}. \n", - "$$\n", - "\n", - "Therefore, the inner-product oracle can be realized by the following unitary transformation, which is decomposable as single-qubit unitaries: \n", - "\n", - "$$\n", - "O_{f_a} = O^1 \\otimes O^2 \\otimes \\ldots \\otimes O^i \\otimes \\ldots \\otimes O^n, \n", - "$$\n", - "where $O^i = (1 - a_i)I + a_i Z$, where $Z$ is the Pauli $Z$ matrix and $I$ is the identity matrix for $a_i \\in \\{0,1\\}$. \n", - "\n", - "Notice that we start from a separable quantum state $|0\\rangle$ and apply a series of transformations that are separable (i.e., can be described by unitaries acting on a single qubit): Hadamard gates to each qubit, followed by the call to the *decomposable* quantum oracle as [Du et al.](https://arxiv.org/abs/quant-ph/0012114), and another Hadamard gate. Hence, there is no entanglement created during the computation. This is in contrast with the circuit at [Linke et al.](http://www.pnas.org/content/114/13/3305.full) that used CNOT gates to realize the oracle and an ancilla qubit to store the answer of the oracle. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The Circuit \n", - "\n", - "We now implement the Bernstein-Vazirani algorithm with QISKit by first preparing the environment." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-21T10:46:58.578540Z", - "start_time": "2018-06-21T10:46:55.706847Z" - } - }, - "outputs": [], - "source": [ - "from qiskit import *\n", - "register()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We first set the number of qubits used in the experiment, and the hidden integer $a$ to be found by the Bernstein-Vazirani algorithm. The hidden integer $a$ determines the circuit for the quantum oracle. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We then use QISKit to program the Bernstein-Vazirani algorithm." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-21T10:53:04.261863Z", - "start_time": "2018-06-21T10:53:02.374975Z" - } - }, - "outputs": [], - "source": [ - "nQubits = 16 # number of physical qubits\n", - "a = 101 # integer whose bitstring is 1100101\n", - "\n", - "qr = QuantumRegister(nQubits)\n", - "cr = ClassicalRegister(nQubits)\n", - "bvCircuit = QuantumCircuit(qr, cr)\n", - "\n", - "# Apply Hadamard gates before querying the oracle\n", - "for i in range(nQubits): bvCircuit.h(qr[i])\n", - " \n", - "# Apply barrier so that it is not optimized by the compiler\n", - "bvCircuit.barrier()\n", - "\n", - "# Apply the inner-product oracle\n", - "for i in range(nQubits):\n", - " if (a & (1 << i)): bvCircuit.z(qr[i]) \n", - " else: bvCircuit.iden(qr[i])\n", - " \n", - "# Apply barrier \n", - "bvCircuit.barrier()\n", - "\n", - "#Apply Hadamard gates after querying the oracle\n", - "for i in range(nQubits): bvCircuit.h(qr[i])\n", - " \n", - "# Measurement\n", - "for i in range(nQubits): bvCircuit.measure(qr[i], cr[i])\n", - " \n", - "backend = \"local_qasm_simulator\"\n", - "#backend = 'ibmqx5'\n", - "results = execute(bvCircuit, backend=backend).result()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Experiment with Simulators\n", - "\n", - "We can run the above circuit on the simulator. " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/qiskit/wrapper/_register.py b/qiskit/wrapper/_register.py index 00d100d0cb66..cc11778da49b 100644 --- a/qiskit/wrapper/_register.py +++ b/qiskit/wrapper/_register.py @@ -11,7 +11,7 @@ from qiskit import QISKitError from qiskit.backends.ibmq.ibmqprovider import IBMQProvider from qiskit.wrapper.defaultqiskitprovider import DefaultQISKitProvider -from qiskit.wrapper.configrc import _settings as rc_set +from qiskit.wrapper.credentials import _settings as rc_set # Default provider used by the rest of the functions on this module. Please # note that this is a global object. diff --git a/qiskit/wrapper/configrc/__init__.py b/qiskit/wrapper/configrc/__init__.py deleted file mode 100644 index 7c6d72e116e0..000000000000 --- a/qiskit/wrapper/configrc/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- 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. diff --git a/qiskit/wrapper/configrc/_configrc.py b/qiskit/wrapper/configrc/_configrc.py deleted file mode 100644 index 20e5796db05d..000000000000 --- a/qiskit/wrapper/configrc/_configrc.py +++ /dev/null @@ -1,405 +0,0 @@ -# -*- 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. - -""" -A module for generating and manipulating the qiskitrc file. -""" -import os -import ast -import importlib.util -import configparser -from qiskit import QISKitError -from qiskit.wrapper._register import _register -from qiskit.wrapper.configrc import _settings as rc_set - - -def has_qiskit_configrc(): - """ - Checks to see if the qikitrc file exists in the default - location, i.e. HOME/.qiskit/qiskitrc - - Returns: - bool: True if file exists, False otherwise. - """ - has_rc = False - qiskit_conf_dir = os.path.join(os.path.expanduser("~"), '.qiskit') - if os.path.exists(qiskit_conf_dir): - qiskit_rc_file = os.path.join(qiskit_conf_dir, 'qiskitrc') - qrc_exists = os.path.isfile(qiskit_rc_file) - if qrc_exists: - has_rc = True - rc_set.QISKIT_RC_FILE = qiskit_rc_file - return has_rc - - -def generate_qiskitrc(overwrite=False): - """ - Generate a blank qiskitrc file. - - Args: - overwrite (bool): Overwrite existing file. Default is False. - - Returns: - bool: True if file was written, False otherwise. - - Raises: - QISKitError: - Could not write to user home directory. - """ - # Check for write access to home dir - if not os.access(os.path.expanduser("~"), os.W_OK): - raise QISKitError('No write access to home directory.') - qiskit_conf_dir = os.path.join(os.path.expanduser("~"), '.qiskit') - if not os.path.exists(qiskit_conf_dir): - try: - os.mkdir(qiskit_conf_dir) - except Exception: - raise QISKitError( - 'Unable to write QISKit config file to home directory.') - rc_set.QISKIT_RC_FILE = os.path.join(qiskit_conf_dir, 'qiskitrc') - qrc_exists = os.path.isfile(rc_set.QISKIT_RC_FILE) - if qrc_exists: - if overwrite: - os.remove(rc_set.QISKIT_RC_FILE) - else: - return False - # Write a basic file with REGISTRATION section - cfgfile = open(rc_set.QISKIT_RC_FILE, 'w') - config = configparser.ConfigParser() - config.add_section('REGISTRATION') - config.write(cfgfile) - cfgfile.close() - return True - - -def has_qiskitrc_key(section, key): - """ - Checks if a given key is already in a given - section of the configrc. - - Args: - section (str): Section in which to look. - key (str): Key of interest. - - Returns: - bool: True if key exits, False otherwise. - - Raises: - QISKitError: - Config file not found. - """ - out = False - if rc_set.QISKIT_RC_FILE is None: - raise QISKitError('QISKit config file not found.') - config = configparser.ConfigParser() - config.read(rc_set.QISKIT_RC_FILE) - if config.has_section(section): - if key in config.options(section): - out = True - else: - raise QISKitError('Section %s not found in qiskitrc.' % section) - return out - - -def write_qiskitrc_key(section, key, value, overwrite=False): - """ - Writes a single key value to the qiskitrc file. - - Args: - section (str): Section in which to write. - key (str): Key to be written. - value (str): Value to be written - overwrite (bool): Overwrite key if it exists. Default is False. - - Raises: - QISKitError: - Config file not found. - """ - if rc_set.QISKIT_RC_FILE is None: - raise QISKitError('QISKit config file not found.') - config = configparser.ConfigParser() - config.read(rc_set.QISKIT_RC_FILE) - if not config.has_section(section): - config.add_section(section) - if has_qiskitrc_key(section, key) and (not overwrite): - raise QISKitError('%s is already present and overwrite=False' % key) - elif has_qiskitrc_key(section, key): - config.remove_option(section, key) - config.set(section, key, str(value)) - cfgfile = open(rc_set.QISKIT_RC_FILE, 'w') - config.write(cfgfile) - cfgfile.close() - - -def read_qiskitrc_key(section, key): - """ - Reads a single key value from the qiskitrc file. - - Parameters: - section (str): The section to search. - key (str): The key whose value we want. - - Returns: - value: Value contained in section:key. - - Raises: - QISKitError: - Config file not found. - """ - if not has_qiskit_configrc(): - raise QISKitError('QISKit config file not found.') - config = configparser.ConfigParser() - config.read(rc_set.QISKIT_RC_FILE) - if not config.has_section(section): - raise QISKitError('Section %s is missing from qiskitrc.' % section) - if key not in config.options(section): - raise QISKitError('Key %s is missing in section %s in qiskitrc.' % (key, section)) - # Need to do an eval here in the case value is a dict. - out = ast.literal_eval(config.get(section, key)) - return out - - -def store_credentials(token=None, - url='https://quantumexperience.ng.bluemix.net/api', - hub=None, group=None, project=None, proxies=None, - verify=True, overwrite=False): - """Store provider credentials in local qiskitrc. - - Args: - token (str): The token used to register on the online backend such - as the quantum experience. - url (str): The url used for online backend such as the quantum - experience. - hub (str): The hub used for online backend. - group (str): The group used for online backend. - project (str): The project used for online backend. - proxies (dict): Proxy configuration for the API, as a dict with - 'urls' and credential keys. - verify (bool): If False, ignores SSL certificates errors. - overwrite (bool): Overwrite existing credentials, default is False. - Raises: - QISKitError: If provider already exists and overwrite=False. - """ - if 'quantumexperience' in url: - provider_name = 'ibmq' - elif 'q-console' in url: - provider_name = 'qnet' - else: - raise QISKitError('Cannot parse provider name from credentials.') - pro_dict = {'token': token, 'url': url, - 'hub': hub, 'group': group, 'project': project, - 'proxies': proxies, 'verify': verify} - if not has_qiskit_configrc(): - generate_qiskitrc() - if has_qiskitrc_key('REGISTRATION', provider_name) and (not overwrite): - raise QISKitError('%s is already present and overwrite=False' - % provider_name) - write_qiskitrc_key('REGISTRATION', provider_name, - pro_dict, overwrite=True) - - -def get_credentials(provider_name=None): - """Get the provider credentials - stored in qiskitrc. - - If no provider_name given, returns list - of provider names in qiskitrc. - - Args: - provider_name (str): Name of provider. - - Returns: - list: List of providers in qiskitrc if - provider_name=None. - - Raises: - QISKitError: If missing section or qiskitrc file. - """ - if provider_name is not None: - return read_qiskitrc_key('REGISTRATION', provider_name) - else: # List all stored provider names - if has_qiskit_configrc(): - config = configparser.ConfigParser() - config.read(rc_set.QISKIT_RC_FILE) - section = 'REGISTRATION' - if not config.has_section(section): - raise QISKitError('Section %s is missing from qiskitrc.' % section) - else: - return config.options(section) - else: - raise QISKitError('qiskitrc file not found.') - - -def remove_credentials(provider_name): - """Remove provider credentials - from qiskitrc. - - Args: - provider_name (str): Name of provider. - - Raises: - QISKitError: If missing section and/or provider_name. - """ - if has_qiskit_configrc(): - config = configparser.ConfigParser() - config.read(rc_set.QISKIT_RC_FILE) - section = 'REGISTRATION' - if not config.has_section(section): - raise QISKitError( - 'Section %s is missing from qiskitrc.' % section) - else: - if provider_name not in config.options(section): - raise QISKitError( - 'Key %s is missing in section %s in qiskitrc.' - % (provider_name, section)) - else: - config.remove_option(section, provider_name) - cfgfile = open(rc_set.QISKIT_RC_FILE, 'w') - config.write(cfgfile) - cfgfile.close() - else: - raise QISKitError('qiskitrc file not found.') - - -def qiskitrc_register_providers(specific_provider=None): - """ Registers providers in qiskitrc - - Args: - specific_provider (str): Register only this provider, if any. - - Raises: - QISKitError: if the qiskitrc file cannot be read. - """ - if rc_set.QISKIT_RC_FILE is None: - raise QISKitError('QISKit config file not found.') - config = configparser.ConfigParser() - config.read(rc_set.QISKIT_RC_FILE) - if not config.has_section('REGISTRATION'): - raise QISKitError("Missing 'REGISTRATION' section in qiskitrc") - did_register = 0 - for pro in config.options('REGISTRATION'): - pro_dict = ast.literal_eval(config.get('REGISTRATION', pro)) - if specific_provider is None: - _register(**pro_dict) - did_register = 1 - elif pro == specific_provider: - _register(**pro_dict) - did_register = 1 - if not did_register and specific_provider is not None: - raise QISKitError('Provider %s credentials not found.' % specific_provider) - - -def get_qconfig_credentials(provider_name=None, - specific_provider=False): - """ - Looks for registration information in a Qconfig.py - file in the cwd. - - Args: - provider_name (str): Name of provider - specific_provider (bool): Is specific provider requested. - - Returns: - bool: Did registration occur. - - Raises: - QISKitError: Error loading Qconfig.py - """ - did_register = 0 - cwd = os.getcwd() - if os.path.isfile(cwd + '/Qconfig.py'): - try: - spec = importlib.util.spec_from_file_location( - "Qconfig", cwd+'/Qconfig.py') - q_config = importlib.util.module_from_spec(spec) - spec.loader.exec_module(q_config) - config_dict = q_config.config - config_dict['token'] = q_config.APItoken - if specific_provider: - if (provider_name == 'ibmq' and - 'quantumexperience' in config_dict['url']) \ - or (provider_name == 'qnet' and - 'q-console' in config_dict['url']): - _register(**config_dict) - - else: - if not has_qiskit_configrc() and \ - os.environ.get('QE_TOKEN') is None: - raise QISKitError( - 'Provider %s credentials not found.' % specific_provider) - - else: - _register(**config_dict) - did_register = 1 - except Exception: - raise QISKitError('Error loading Qconfig.py') - return did_register - - -def get_env_credentials(provider_name=None, - specific_provider=False): - """ - Looks for registration information in the environment variables. - - Args: - provider_name (str): Name of provider - specific_provider (bool): Is specific provider requested. - - Returns: - bool: Did registration occur. - - Raises: - QISKitError: Specific provider not found. - """ - did_register = 0 - token = os.environ.get('QE_TOKEN') - url = os.environ.get('QE_URL') - if token is not None: - # We have at least a token so lets load it - if url is None: - url = 'https://quantumexperience.ng.bluemix.net/api' - - if specific_provider: - if (provider_name == 'ibmq' and - 'quantumexperience' in url) \ - or (provider_name == 'qnet' and - 'q-console' in url): - _register(token=token, - url=url, - hub=os.environ.get('QE_HUB'), - group=os.environ.get('QE_GROUP'), - project=os.environ.get('QE_PROJECT')) - else: - if not has_qiskit_configrc(): - raise QISKitError( - 'Provider %s credentials not found.' % provider_name) - else: - _register(token=token, - url=url, - hub=os.environ.get('QE_HUB'), - group=os.environ.get('QE_GROUP'), - project=os.environ.get('QE_PROJECT')) - did_register = 1 - return did_register - - -def get_qiskitrc_credentials(provider_name=None): - """ - Looks for registration information in qiskitrc file. - - Args: - provider_name (str): Name of provider - - Returns: - bool: Did registration occur. - """ - # Look at qiksitrc for saved data - did_register = 0 - if has_qiskit_configrc(): - qiskitrc_register_providers(specific_provider=provider_name) - did_register = 1 - return did_register diff --git a/qiskit/wrapper/credentials/__init__.py b/qiskit/wrapper/credentials/__init__.py new file mode 100644 index 000000000000..2ace212e647b --- /dev/null +++ b/qiskit/wrapper/credentials/__init__.py @@ -0,0 +1,68 @@ +# -*- 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. + +""" +Utilties for working with credentials for the wrapper. +""" +import logging + +from qiskit import QISKitError +from ._configrc import read_credentials_from_qiskitrc +from ._environ import read_credentials_from_environ +from ._qconfig import read_credentials_from_qconfig + + +logger = logging.getLogger(__name__) + + +def discover_credentials(): + """ + Automatically discover credentials for online providers. + + This method looks for credentials in the following locations, in order, + and returning as soon as credentials are found: + 1. in the `Qconfig.py` file in the current working directory. + 2. in the environment variables. + 3. in the `qiskitrc` configuration file. + + Returns: + dict: dictionary with the contents of the configuration file, with + the form:: + + {'provider_name': {'token': 'TOKEN', 'url': 'URL', ... }} + """ + provider_credentials = {} + + # 1. Attempt to read them from the `Qconfig.py` file. + try: + qconfig_credentials = read_credentials_from_qconfig() + if qconfig_credentials: + provider_credentials[None] = qconfig_credentials + return provider_credentials + except QISKitError as ex: + logger.warning( + 'Automatic discovery of qconfig credentials failed: %s', str(ex)) + + # 2. Attempt to read them from the environment variables. + try: + environ_credentials = read_credentials_from_environ() + if environ_credentials: + provider_credentials[None] = environ_credentials + return provider_credentials + except QISKitError as ex: + logger.warning( + 'Automatic discovery of environment credentials failed: %s', + str(ex)) + + # 3. Attempt to read them from the qiskitrc file. + try: + provider_credentials = read_credentials_from_qiskitrc() + except QISKitError as ex: + logger.warning( + 'Automatic discovery of qiskitrc credentials failed: %s', str(ex)) + + return provider_credentials diff --git a/qiskit/wrapper/credentials/_configrc.py b/qiskit/wrapper/credentials/_configrc.py new file mode 100644 index 000000000000..09318da151d5 --- /dev/null +++ b/qiskit/wrapper/credentials/_configrc.py @@ -0,0 +1,144 @@ +# -*- 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 reading and writing credentials from and to configuration files. +""" + +import os +from configparser import ConfigParser, ParsingError +from qiskit import QISKitError + + +DEFAULT_QISKITRC_FILE = os.path.join(os.path.expanduser("~"), + '.qiskit', 'qiskitrc') + + +def read_credentials_from_qiskitrc(filename=None): + """ + Read a configuration file and return a dict with its sections. + + Args: + filename (str): full path to the qiskitrc file. If `None`, the default + location is used (`HOME/.qiskit/qiskitrc`). + + Returns: + dict: dictionary with the contents of the configuration file, with + the form:: + + {'provider_name': {'token': 'TOKEN', 'url': 'URL', ... }} + + Raises: + QISKitError: if the file was not parseable. Please note that this + exception is not raised if the file does not exist (instead, an + empty dict is returned). + """ + filename = filename or DEFAULT_QISKITRC_FILE + config_parser = ConfigParser() + try: + config_parser.read(filename) + except ParsingError as ex: + raise QISKitError(str(ex)) + + return {name: dict(config_parser.items(name)) for + name in config_parser.sections()} + + +def write_qiskit_rc(credentials, filename=None): + """ + Write credentials to the configuration file. + + Args: + credentials (dict): dictionary with the credentials, with the form:: + {'provider_name': {'token': 'TOKEN', 'url': 'URL', ... }} + filename (str): full path to the qiskitrc file. If `None`, the default + location is used (`HOME/.qiskit/qiskitrc`). + """ + filename = filename or DEFAULT_QISKITRC_FILE + # Create the directories and the file if not found. + os.makedirs(os.path.dirname(filename), exist_ok=True) + + # Write the configuration file. + with open(filename, 'w') as config_file: + config_parser = ConfigParser() + config_parser.read_dict(credentials) + config_parser.write(config_file) + + +def store_credentials(token=None, + url='https://quantumexperience.ng.bluemix.net/api', + hub=None, group=None, project=None, proxies=None, + verify=True, account_name=None, overwrite=False, + filename=None): + """ + Store the credentials for a single provider in the configuration file. + + Args: + token (str): the token used to register on the online backend such + as the quantum experience. + url (str): the url used for online backend such as the quantum + experience. + hub (str): the hub used for online backend. + group (str): the group used for online backend. + project (str): the project used for online backend. + proxies (dict): proxy configuration for the API, as a dict with + 'urls' and credential keys. + verify (bool): if False, ignores SSL certificates errors. + account_name (str): name for the account in the configuration file + section. + overwrite (bool): overwrite existing credentials. + filename (str): full path to the qiskitrc file. If `None`, the default + location is used (`HOME/.qiskit/qiskitrc`). + + Raises: + QISKitError: If provider already exists and overwrite=False; or if + the account_name could not be assigned. + """ + # Assign a default name for the credentials section. + if not account_name: + if 'quantumexperience' in url: + account_name = 'ibmq' + elif 'q-console' in url: + account_name = 'qnet' + else: + raise QISKitError('Cannot parse provider name from credentials.') + + # Read the current providers stored in the configuration file. + filename = filename or DEFAULT_QISKITRC_FILE + credentials = read_credentials_from_qiskitrc(filename) + if account_name in credentials.keys() and not overwrite: + raise QISKitError('%s is already present and overwrite=False' + % account_name) + + # Append the provider, and store it in the file. + credentials[account_name] = { + 'token': token, 'url': url, 'hub': hub, 'group': group, + 'project': project, 'proxies': proxies, 'verify': verify} + write_qiskit_rc(credentials, filename) + + +def remove_credentials(account_name, filename=None): + """Remove provider credentials from qiskitrc. + + Args: + account_name (str): Name of the account to be removed. + filename (str): full path to the qiskitrc file. If `None`, the default + location is used (`HOME/.qiskit/qiskitrc`). + + Raises: + QISKitError: If there is no account with that name on the configuration + file. + """ + credentials = read_credentials_from_qiskitrc(filename) + + try: + credentials.pop(account_name) + except KeyError: + raise QISKitError('The account "%s" does not exist in the ' + 'configuration file. Available accounts: %s' % + (account_name, credentials.keys())) + write_qiskit_rc(credentials, filename) diff --git a/qiskit/wrapper/credentials/_environ.py b/qiskit/wrapper/credentials/_environ.py new file mode 100644 index 000000000000..0dbceab41de3 --- /dev/null +++ b/qiskit/wrapper/credentials/_environ.py @@ -0,0 +1,44 @@ +# -*- 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 reading credentials from environment variables. +""" + +import os + +# Dictionary that maps `ENV_VARIABLE_NAME` to credential parameter. +VARIABLES_MAP = { + 'QE_TOKEN': 'token', + 'QE_URL': 'url', + 'QE_HUB': 'hub', + 'QE_GROUP': 'group', + 'QE_PROJECT': 'project' +} + + +def read_credentials_from_environ(): + """ + Read the environment variables and return its credentials. + + Returns: + dict: dictionary with the credentials, in the form:: + + {'token': 'TOKEN', 'url': 'URL', ... } + + """ + # The token is the only required parameter. + if not os.getenv('QE_TOKEN'): + return {} + + # Build the credentials based on environment variables. + credentials = {} + for envar_name, credential_key in VARIABLES_MAP.items(): + if os.getenv(envar_name): + credentials[credential_key] = os.getenv(envar_name) + + return credentials diff --git a/qiskit/wrapper/credentials/_qconfig.py b/qiskit/wrapper/credentials/_qconfig.py new file mode 100644 index 000000000000..31e00a573f5e --- /dev/null +++ b/qiskit/wrapper/credentials/_qconfig.py @@ -0,0 +1,55 @@ +# -*- 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 reading credentials from the deprecated `Qconfig.py` file. +""" + +from importlib.util import spec_from_file_location, module_from_spec +import os + +from qiskit import QISKitError + + +def read_credentials_from_qconfig(): + """ + Read a `QConfig.py` file and return its credentials. + + Returns: + dict: dictionary with the credentials, in the form:: + + {'token': 'TOKEN', 'url': 'URL', ... } + + Raises: + QISKitError: if the Qconfig.py was not parseable. Please note that this + exception is not raised if the file does not exist (instead, an + empty dict is returned). + """ + if not os.path.isfile('Qconfig.py'): + return {} + else: + # Note this is nested inside the else to prevent some tools marking + # the whole method as deprecated. + pass + # TODO: reintroduce when we decide on deprecatin + # warnings.warn( + # "Using 'Qconfig.py' for storing the credentials will be deprecated in" + # "upcoming versions (>0.6.0). Using .qiskitrc is recommended", + # DeprecationWarning) + + try: + spec = spec_from_file_location('Qconfig', 'Qconfig.py') + q_config = module_from_spec(spec) + spec.loader.exec_module(q_config) + + credentials = q_config.config.copy() + credentials['token'] = q_config.APItoken + except Exception as ex: + # pylint: disable=broad-except + raise QISKitError('Error loading Qconfig.py: %s' % str(ex)) + + return credentials diff --git a/qiskit/wrapper/configrc/_settings.py b/qiskit/wrapper/credentials/_settings.py similarity index 92% rename from qiskit/wrapper/configrc/_settings.py rename to qiskit/wrapper/credentials/_settings.py index 5133c6efbd10..652158fab627 100644 --- a/qiskit/wrapper/configrc/_settings.py +++ b/qiskit/wrapper/credentials/_settings.py @@ -8,6 +8,4 @@ """Module that holds the runtime location of qiskitrc file. """ -QISKIT_RC_FILE = None - REGISTER_CALLED = 0 diff --git a/test/python/test_registration.py b/test/python/test_registration.py index ec0048602d41..3df3f79ea4a5 100644 --- a/test/python/test_registration.py +++ b/test/python/test_registration.py @@ -10,11 +10,11 @@ """Tests for QISKit API registration""" import os import qiskit.wrapper._wrapper as wrap -from qiskit.wrapper.configrc._configrc import (has_qiskit_configrc, - generate_qiskitrc, - store_credentials, - get_credentials, - remove_credentials) +from qiskit.wrapper.credentials._configrc import (has_qiskit_configrc, + generate_qiskitrc, + store_credentials, + get_credentials, + remove_credentials) from .common import (QiskitTestCase, requires_ci, requires_qe_access) From 4c02c376cce2dbf7e08c7e68770dcf35c7086ff0 Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Tue, 10 Jul 2018 14:31:00 +0200 Subject: [PATCH 3/6] Add autoregistration in register(), tests Revise the wrapper `register()` functionality: * allow autoregistration for the IBMQProvider if the function is called without parameters (no args or kwargs), using the `credentials.*` methods for trying to find a working configuration. * add `store_credentials()` method, for allowing the user to store credentials in the qiskitrc file. Update the `credentials.read_credentials_from_x` to unify the return types, allowing for only one IBMQProvider credentials. Fix bug with Qconfig.config and with parsing of the proxies parameter. Revise tests, including context manager for mocking the different configuration locations, and adding tests for the autoregistration and the credentials discovery. --- qiskit/__init__.py | 7 +- qiskit/wrapper/__init__.py | 2 +- qiskit/wrapper/_register.py | 58 ----- qiskit/wrapper/_wrapper.py | 47 +++- qiskit/wrapper/credentials/__init__.py | 19 +- qiskit/wrapper/credentials/_configrc.py | 72 +++--- qiskit/wrapper/credentials/_environ.py | 7 +- qiskit/wrapper/credentials/_qconfig.py | 17 +- test/python/common.py | 44 ++-- test/python/test_registration.py | 281 ++++++++++++++---------- 10 files changed, 299 insertions(+), 255 deletions(-) delete mode 100644 qiskit/wrapper/_register.py diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 3186649a6255..438f13352739 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -10,6 +10,8 @@ """Main QISKit public functionality.""" +import os + # First, check for required Python and API version from . import _util @@ -35,13 +37,12 @@ from .wrapper._wrapper import ( available_backends, local_backends, remote_backends, get_backend, compile, execute, register, unregister, - registered_providers, load_qasm_string, load_qasm_file, least_busy) + registered_providers, load_qasm_string, load_qasm_file, least_busy, + store_credentials) # Import the wrapper, to make it available when doing "import qiskit". from . import wrapper -import os - ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) with open(os.path.join(ROOT_DIR, "VERSION.txt"), "r") as version_file: __version__ = version_file.read().strip() diff --git a/qiskit/wrapper/__init__.py b/qiskit/wrapper/__init__.py index e647ab157a20..8a6926a49840 100644 --- a/qiskit/wrapper/__init__.py +++ b/qiskit/wrapper/__init__.py @@ -18,4 +18,4 @@ from ._wrapper import (available_backends, local_backends, remote_backends, get_backend, compile, execute, register, unregister, registered_providers, load_qasm_string, load_qasm_file, - least_busy) + least_busy, store_credentials) diff --git a/qiskit/wrapper/_register.py b/qiskit/wrapper/_register.py deleted file mode 100644 index cc11778da49b..000000000000 --- a/qiskit/wrapper/_register.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- 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. - -"""Internal register function""" - -import warnings -from qiskit import QISKitError -from qiskit.backends.ibmq.ibmqprovider import IBMQProvider -from qiskit.wrapper.defaultqiskitprovider import DefaultQISKitProvider -from qiskit.wrapper.credentials import _settings as rc_set - -# Default provider used by the rest of the functions on this module. Please -# note that this is a global object. -_DEFAULT_PROVIDER = DefaultQISKitProvider() - - -def _register(token=None, url='https://quantumexperience.ng.bluemix.net/api', - hub=None, group=None, project=None, proxies=None, verify=True): - """ - Internal function that calls the actual providers. - Args: - token (str): The token used to register on the online backend such - as the quantum experience. - url (str): The url used for online backend such as the quantum - experience. - hub (str): The hub used for online backend. - group (str): The group used for online backend. - project (str): The project used for online backend. - proxies (dict): Proxy configuration for the API, as a dict with - 'urls' and credential keys. - verify (bool): If False, ignores SSL certificates errors. - Raises: - QISKitError: if the provider name is not recognized. - """ - _registered_names = [p.name for p in _DEFAULT_PROVIDER.providers] - if 'quantumexperience' in url: - provider_name = 'ibmq' - elif 'q-console' in url: - provider_name = 'qnet' - else: - raise QISKitError('Unkown provider name.') - if provider_name not in _registered_names: - try: - provider = IBMQProvider(token, url, - hub, group, project, - proxies, verify) - _DEFAULT_PROVIDER.add_provider(provider) - except ConnectionError: - warnings.warn('%s not registered. No connection established.' % provider_name) - else: - if rc_set.REGISTER_CALLED: - warnings.warn( - "%s already registered. Use unregister('%s') to remove." % ( - provider_name, provider_name)) diff --git a/qiskit/wrapper/_wrapper.py b/qiskit/wrapper/_wrapper.py index df08cf8ae217..920c0f17415f 100644 --- a/qiskit/wrapper/_wrapper.py +++ b/qiskit/wrapper/_wrapper.py @@ -7,9 +7,11 @@ """Helper module for simplified QISKit usage.""" +import logging import warnings from qiskit import transpiler, QISKitError from qiskit.backends.ibmq import IBMQProvider +from qiskit.wrapper import credentials from qiskit.wrapper.defaultqiskitprovider import DefaultQISKitProvider from ._circuittoolkit import circuit_from_qasm_file, circuit_from_qasm_string @@ -19,6 +21,9 @@ _DEFAULT_PROVIDER = DefaultQISKitProvider() +logger = logging.getLogger(__name__) + + def register(*args, provider_class=IBMQProvider, **kwargs): """ Authenticate against an online backend provider. @@ -45,9 +50,17 @@ def register(*args, provider_class=IBMQProvider, **kwargs): BaseProvider: the provider instance that was just registered. Raises: - QISKitError: if the provider could not be registered - (e.g. due to conflict) + QISKitError: if the provider could not be registered (e.g. due to + conflict, or if no credentials were provided.) """ + # Try to autodiscover credentials if not passed. + if not args and not kwargs and provider_class == IBMQProvider: + kwargs = credentials.discover_credentials().get(IBMQProvider.__name__) or {} + if not kwargs: + raise QISKitError( + 'No IBMQ credentials found. Please pass them explicitly or ' + 'store them before calling register() with store_credentials()') + try: provider = provider_class(*args, **kwargs) except Exception as ex: @@ -79,8 +92,36 @@ def registered_providers(): return list(_DEFAULT_PROVIDER.providers) -# Functions for inspecting and retrieving backends. +def store_credentials(token, url='https://quantumexperience.ng.bluemix.net/api', + hub=None, group=None, project=None, proxies=None, + verify=True, overwrite=False): + """ + Store credentials for the IBMQ account in the config file. + + Args: + token (str): The token used to register on the online backend such + as the quantum experience. + url (str): The url used for online backend such as the quantum + experience. + hub (str): The hub used for online backend. + group (str): The group used for online backend. + project (str): The project used for online backend. + proxies (dict): Proxy configuration for the API, as a dict with + 'urls' and credential keys. + verify (bool): If False, ignores SSL certificates errors. + overwrite (bool): overwrite existing credentials. + + Raises: + QISKitError: if the credentials already exist and overwrite==False. + """ + credentials.store_credentials( + provider_class=IBMQProvider, overwrite=overwrite, + token=token, url=url, hub=hub, group=group, project=project, + proxies=proxies, verify=verify + ) + +# Functions for inspecting and retrieving backends. def available_backends(filters=None, compact=True): """ diff --git a/qiskit/wrapper/credentials/__init__.py b/qiskit/wrapper/credentials/__init__.py index 2ace212e647b..19b7cad61aba 100644 --- a/qiskit/wrapper/credentials/__init__.py +++ b/qiskit/wrapper/credentials/__init__.py @@ -6,12 +6,12 @@ # the LICENSE.txt file in the root directory of this source tree. """ -Utilties for working with credentials for the wrapper. +Utilities for working with credentials for the wrapper. """ import logging from qiskit import QISKitError -from ._configrc import read_credentials_from_qiskitrc +from ._configrc import read_credentials_from_qiskitrc, store_credentials from ._environ import read_credentials_from_environ from ._qconfig import read_credentials_from_qconfig @@ -35,14 +35,12 @@ def discover_credentials(): {'provider_name': {'token': 'TOKEN', 'url': 'URL', ... }} """ - provider_credentials = {} - # 1. Attempt to read them from the `Qconfig.py` file. try: qconfig_credentials = read_credentials_from_qconfig() if qconfig_credentials: - provider_credentials[None] = qconfig_credentials - return provider_credentials + logger.info('Using credentials from qconfig') + return qconfig_credentials except QISKitError as ex: logger.warning( 'Automatic discovery of qconfig credentials failed: %s', str(ex)) @@ -51,8 +49,8 @@ def discover_credentials(): try: environ_credentials = read_credentials_from_environ() if environ_credentials: - provider_credentials[None] = environ_credentials - return provider_credentials + logger.info('Using credentials from environment variables') + return environ_credentials except QISKitError as ex: logger.warning( 'Automatic discovery of environment credentials failed: %s', @@ -61,8 +59,11 @@ def discover_credentials(): # 3. Attempt to read them from the qiskitrc file. try: provider_credentials = read_credentials_from_qiskitrc() + if provider_credentials: + logger.info('Using credentials from qiskitrc') + return provider_credentials except QISKitError as ex: logger.warning( 'Automatic discovery of qiskitrc credentials failed: %s', str(ex)) - return provider_credentials + return {} diff --git a/qiskit/wrapper/credentials/_configrc.py b/qiskit/wrapper/credentials/_configrc.py index 09318da151d5..ea1b68fd973b 100644 --- a/qiskit/wrapper/credentials/_configrc.py +++ b/qiskit/wrapper/credentials/_configrc.py @@ -10,8 +10,11 @@ """ import os +from ast import literal_eval from configparser import ConfigParser, ParsingError + from qiskit import QISKitError +from qiskit.backends.ibmq import IBMQProvider DEFAULT_QISKITRC_FILE = os.path.join(os.path.expanduser("~"), @@ -30,7 +33,7 @@ def read_credentials_from_qiskitrc(filename=None): dict: dictionary with the contents of the configuration file, with the form:: - {'provider_name': {'token': 'TOKEN', 'url': 'URL', ... }} + {'provider_class_name': {'token': 'TOKEN', 'url': 'URL', ... }} Raises: QISKitError: if the file was not parseable. Please note that this @@ -44,8 +47,18 @@ def read_credentials_from_qiskitrc(filename=None): except ParsingError as ex: raise QISKitError(str(ex)) - return {name: dict(config_parser.items(name)) for - name in config_parser.sections()} + # Build the credentials dictionary. + credentials_dict = {} + for name in config_parser.sections(): + single_credentials = dict(config_parser.items(name)) + # TODO: 'proxies' is the only value that is a dict. Consider moving to + # json configuration or splitting into single keys manually. + if 'proxies' in single_credentials.keys(): + single_credentials['proxies'] = literal_eval( + single_credentials['proxies']) + credentials_dict[name] = single_credentials + + return credentials_dict def write_qiskit_rc(credentials, filename=None): @@ -54,7 +67,7 @@ def write_qiskit_rc(credentials, filename=None): Args: credentials (dict): dictionary with the credentials, with the form:: - {'provider_name': {'token': 'TOKEN', 'url': 'URL', ... }} + {'provider_class_name': {'token': 'TOKEN', 'url': 'URL', ... }} filename (str): full path to the qiskitrc file. If `None`, the default location is used (`HOME/.qiskit/qiskitrc`). """ @@ -69,44 +82,25 @@ def write_qiskit_rc(credentials, filename=None): config_parser.write(config_file) -def store_credentials(token=None, - url='https://quantumexperience.ng.bluemix.net/api', - hub=None, group=None, project=None, proxies=None, - verify=True, account_name=None, overwrite=False, - filename=None): +def store_credentials(provider_class=IBMQProvider, overwrite=False, + filename=None, **kwargs): """ Store the credentials for a single provider in the configuration file. Args: - token (str): the token used to register on the online backend such - as the quantum experience. - url (str): the url used for online backend such as the quantum - experience. - hub (str): the hub used for online backend. - group (str): the group used for online backend. - project (str): the project used for online backend. - proxies (dict): proxy configuration for the API, as a dict with - 'urls' and credential keys. - verify (bool): if False, ignores SSL certificates errors. - account_name (str): name for the account in the configuration file - section. + provider_class (class): class of the Provider for the credentials. overwrite (bool): overwrite existing credentials. filename (str): full path to the qiskitrc file. If `None`, the default location is used (`HOME/.qiskit/qiskitrc`). + kwargs (dict): keyword arguments passed to provider class + initialization. Raises: QISKitError: If provider already exists and overwrite=False; or if the account_name could not be assigned. """ - # Assign a default name for the credentials section. - if not account_name: - if 'quantumexperience' in url: - account_name = 'ibmq' - elif 'q-console' in url: - account_name = 'qnet' - else: - raise QISKitError('Cannot parse provider name from credentials.') - + # Set the name of the Provider from the class. + account_name = provider_class.__name__ # Read the current providers stored in the configuration file. filename = filename or DEFAULT_QISKITRC_FILE credentials = read_credentials_from_qiskitrc(filename) @@ -114,18 +108,17 @@ def store_credentials(token=None, raise QISKitError('%s is already present and overwrite=False' % account_name) - # Append the provider, and store it in the file. - credentials[account_name] = { - 'token': token, 'url': url, 'hub': hub, 'group': group, - 'project': project, 'proxies': proxies, 'verify': verify} + # Append the provider, trim the empty options and store it in the file. + kwargs = {key: value for key, value in kwargs.items() if value is not None} + credentials[account_name] = {**kwargs} write_qiskit_rc(credentials, filename) -def remove_credentials(account_name, filename=None): +def remove_credentials(provider_class=IBMQProvider, filename=None): """Remove provider credentials from qiskitrc. - Args: - account_name (str): Name of the account to be removed. + Args: + provider_class (class): class of the Provider for the credentials. filename (str): full path to the qiskitrc file. If `None`, the default location is used (`HOME/.qiskit/qiskitrc`). @@ -133,12 +126,13 @@ def remove_credentials(account_name, filename=None): QISKitError: If there is no account with that name on the configuration file. """ + # Set the name of the Provider from the class. + account_name = provider_class.__name__ credentials = read_credentials_from_qiskitrc(filename) try: credentials.pop(account_name) except KeyError: raise QISKitError('The account "%s" does not exist in the ' - 'configuration file. Available accounts: %s' % - (account_name, credentials.keys())) + 'configuration file') write_qiskit_rc(credentials, filename) diff --git a/qiskit/wrapper/credentials/_environ.py b/qiskit/wrapper/credentials/_environ.py index 0dbceab41de3..c344f3d21445 100644 --- a/qiskit/wrapper/credentials/_environ.py +++ b/qiskit/wrapper/credentials/_environ.py @@ -11,6 +11,9 @@ import os +from qiskit.backends.ibmq import IBMQProvider + + # Dictionary that maps `ENV_VARIABLE_NAME` to credential parameter. VARIABLES_MAP = { 'QE_TOKEN': 'token', @@ -28,7 +31,7 @@ def read_credentials_from_environ(): Returns: dict: dictionary with the credentials, in the form:: - {'token': 'TOKEN', 'url': 'URL', ... } + {'IBMQProvider': {'token': 'TOKEN', 'url': 'URL', ... }} """ # The token is the only required parameter. @@ -41,4 +44,4 @@ def read_credentials_from_environ(): if os.getenv(envar_name): credentials[credential_key] = os.getenv(envar_name) - return credentials + return {IBMQProvider.__name__: credentials} diff --git a/qiskit/wrapper/credentials/_qconfig.py b/qiskit/wrapper/credentials/_qconfig.py index 31e00a573f5e..6e67c4163466 100644 --- a/qiskit/wrapper/credentials/_qconfig.py +++ b/qiskit/wrapper/credentials/_qconfig.py @@ -9,10 +9,14 @@ Utilities for reading credentials from the deprecated `Qconfig.py` file. """ -from importlib.util import spec_from_file_location, module_from_spec import os +from importlib.util import module_from_spec, spec_from_file_location from qiskit import QISKitError +from qiskit.backends.ibmq import IBMQProvider + + +DEFAULT_QCONFIG_FILE = 'Qconfig.py' def read_credentials_from_qconfig(): @@ -29,7 +33,7 @@ def read_credentials_from_qconfig(): exception is not raised if the file does not exist (instead, an empty dict is returned). """ - if not os.path.isfile('Qconfig.py'): + if not os.path.isfile(DEFAULT_QCONFIG_FILE): return {} else: # Note this is nested inside the else to prevent some tools marking @@ -42,14 +46,17 @@ def read_credentials_from_qconfig(): # DeprecationWarning) try: - spec = spec_from_file_location('Qconfig', 'Qconfig.py') + spec = spec_from_file_location('Qconfig', DEFAULT_QCONFIG_FILE) q_config = module_from_spec(spec) spec.loader.exec_module(q_config) - credentials = q_config.config.copy() + if hasattr(q_config, 'config'): + credentials = q_config.config.copy() + else: + credentials = {} credentials['token'] = q_config.APItoken except Exception as ex: # pylint: disable=broad-except raise QISKitError('Error loading Qconfig.py: %s' % str(ex)) - return credentials + return {IBMQProvider.__name__: credentials} diff --git a/test/python/common.py b/test/python/common.py index 9a501c4555ac..60f8c7fa4fdc 100644 --- a/test/python/common.py +++ b/test/python/common.py @@ -15,6 +15,7 @@ import unittest from unittest.util import safe_repr from qiskit import __path__ as qiskit_path +from qiskit.wrapper.credentials import discover_credentials from qiskit.wrapper.defaultqiskitprovider import DefaultQISKitProvider @@ -227,7 +228,7 @@ def requires_qe_access(func): * determines if the test should be skipped by checking environment variables. * if the test is not skipped, it reads `QE_TOKEN` and `QE_URL` from - `Qconfig.py` or from environment variables. + `Qconfig.py`, environment variables or qiskitrc. * if the test is not skipped, it appends `QE_TOKEN` and `QE_URL` as arguments to the test function. Args: @@ -243,31 +244,24 @@ def _(*args, **kwargs): if SKIP_ONLINE_TESTS: raise unittest.SkipTest('Skipping online tests') - # Try to read the variables from Qconfig. + # Cleanup the credentials, as this file is shared by the tests. + from qiskit.wrapper import _wrapper + _wrapper._DEFAULT_PROVIDER = DefaultQISKitProvider() + + # Attempt to read the standard credentials. + discovered_credentials = discover_credentials() try: - import Qconfig - QE_TOKEN = Qconfig.APItoken - QE_URL = Qconfig.config['url'] - QE_HUB = Qconfig.config.get('hub') - QE_GROUP = Qconfig.config.get('group') - QE_PROJECT = Qconfig.config.get('project') - except ImportError: - # Try to read them from environment variables (ie. Travis). - QE_TOKEN = os.getenv('QE_TOKEN') - QE_URL = os.getenv('QE_URL') - QE_HUB = os.getenv('QE_HUB') - QE_GROUP = os.getenv('QE_GROUP') - QE_PROJECT = os.getenv('QE_PROJECT') - if not QE_TOKEN or not QE_URL: - raise Exception( - 'Could not locate a valid "Qconfig.py" file nor read the QE ' - 'values from the environment') - - kwargs['QE_TOKEN'] = QE_TOKEN - kwargs['QE_URL'] = QE_URL - kwargs['hub'] = QE_HUB - kwargs['group'] = QE_GROUP - kwargs['project'] = QE_PROJECT + credentials = next(iter(discovered_credentials.values())) + kwargs.update({ + 'QE_TOKEN': credentials.get('token'), + 'QE_URL': credentials.get('url'), + 'hub': credentials.get('hub'), + 'group': credentials.get('group'), + 'project': credentials.get('project'), + }) + except StopIteration: + raise Exception('Could not locate valid credentials') + return func(*args, **kwargs) return _ diff --git a/test/python/test_registration.py b/test/python/test_registration.py index 3df3f79ea4a5..8b597bc139bb 100644 --- a/test/python/test_registration.py +++ b/test/python/test_registration.py @@ -1,118 +1,179 @@ # -*- coding: utf-8 -*- -# Copyright 2017, IBM. +# 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. -# pylint: disable=invalid-name, unused-argument +# pylint: disable=invalid-name -"""Tests for QISKit API registration""" +""" +Test the registration and credentials features of the wrapper. +""" import os -import qiskit.wrapper._wrapper as wrap -from qiskit.wrapper.credentials._configrc import (has_qiskit_configrc, - generate_qiskitrc, - store_credentials, - get_credentials, - remove_credentials) -from .common import (QiskitTestCase, requires_ci, requires_qe_access) - - -class TestRegistration(QiskitTestCase): - """Tests for QISKit API registration""" - - @requires_ci - def test_amake_qiskitrc(self): - """Generated qiskitrc file.""" - self.assertTrue(not has_qiskit_configrc()) - generate_qiskitrc() - self.assertTrue(has_qiskit_configrc()) - - @requires_ci - def test_write_qiskitrc_data(self): - """Add provider info to qiskitc""" - generate_qiskitrc(overwrite=True) - self.assertTrue(len(get_credentials()) == 0) - store_credentials(token='abcdefg') - self.assertTrue(len(get_credentials()) == 1) - - @requires_ci - def test_get_qiskitrc_data(self): - """Get provider info from qiskitc""" - generate_qiskitrc(overwrite=True) - store_credentials(token='abcdefg') - _creds = get_credentials('ibmq') - self.assertTrue(_creds['token'] == 'abcdefg') - - @requires_ci - def test_remove_qiskitrc_data(self): - """Remove provider info from qiskitc""" - generate_qiskitrc(overwrite=True) - store_credentials(token='abcdefg') - self.assertTrue(len(get_credentials()) == 1) - remove_credentials('ibmq') - self.assertTrue(len(get_credentials()) == 0) - - @requires_ci - @requires_qe_access - def test_register_from_env(self, QE_TOKEN, QE_URL, - hub=None, group=None, project=None): - """Register from env vars""" - # Remove all providers execept core - pro = wrap.available_providers() - for p in pro: - if p != 'terra': - wrap.unregister(p) - # Make sure blank qiskitrc - generate_qiskitrc(overwrite=True) - orig_back = len(wrap.available_backends()) - wrap.register() - new_back = len(wrap.available_backends()) - self.assertTrue(new_back > orig_back) - - @requires_ci - @requires_qe_access - def test_register_from_qiskitrc(self, QE_TOKEN, QE_URL, - hub=None, group=None, project=None): - """Register from qiskitrc""" - # Remove all providers execept core - pro = wrap.available_providers() - for p in pro: - if p != 'terra': - wrap.unregister(p) - orig_back = len(wrap.available_backends()) - # Make sure blank qiskitrc - generate_qiskitrc(overwrite=True) - _token = os.getenv('QE_TOKEN') - # Remove env var so that it does not trigger - # env var registration - del os.environ['QE_TOKEN'] - store_credentials(token=QE_TOKEN, - url=QE_URL, - hub=hub, - group=group, - project=project) - wrap.register() - new_back = len(wrap.available_backends()) - self.assertTrue(new_back > orig_back) - # Put token back in env - os.environ['QE_TOKEN'] = _token - - @requires_ci - @requires_qe_access - def test_save_from_register(self, QE_TOKEN, QE_URL, - hub=None, group=None, project=None): - """Save credentials when calling register""" - # Remove all providers execept core - pro = wrap.available_providers() - for p in pro: - if p != 'terra': - wrap.unregister(p) - # Make sure blank qiskitrc - generate_qiskitrc(overwrite=True) - wrap.register(token=QE_TOKEN, - url=QE_URL, - hub=hub, group=group, project=project, - save_credentials=True) - _creds = get_credentials() - self.assertTrue(len(_creds) > 0) +from contextlib import contextmanager +from tempfile import NamedTemporaryFile +from unittest.mock import patch +from unittest import skipIf + +import qiskit +from qiskit import QISKitError +from qiskit.backends.ibmq import IBMQProvider +from qiskit.wrapper.credentials import _configrc, _qconfig, discover_credentials +from qiskit.wrapper.credentials._environ import VARIABLES_MAP +from .common import QiskitTestCase + + +# TODO: NamedTemporaryFiles do not support name in Windows +@skipIf(os.name == 'nt', 'Test not supported in Windows') +class TestWrapperCredentuals(QiskitTestCase): + """Wrapper autoregistration and credentials test case.""" + def test_autoregister_no_credentials(self): + """Test register() with no credentials available.""" + with no_file('Qconfig.py'), no_file(_configrc.DEFAULT_QISKITRC_FILE), no_envs(): + with self.assertRaises(QISKitError) as cm: + qiskit.wrapper.register() + + self.assertIn('No IBMQ credentials found', str(cm.exception)) + + def test_store_credentials(self): + """Test storing credentials and using them for autoregister.""" + with no_file('Qconfig.py'), no_envs(), custom_qiskitrc(), mock_ibmq_provider(): + qiskit.wrapper.store_credentials('QISKITRC_TOKEN', proxies={'http': 'foo'}) + provider = qiskit.register() + + self.assertEqual(provider._token, 'QISKITRC_TOKEN') + self.assertEqual(provider._proxies, {'http': 'foo'}) + + def test_store_credentials_overwrite(self): + """Test overwritind qiskitrc credentials.""" + with custom_qiskitrc(): + qiskit.wrapper.store_credentials('QISKITRC_TOKEN', hub='HUB') + # Attempt overwriting. + with self.assertRaises(QISKitError) as cm: + qiskit.wrapper.store_credentials('QISKITRC_TOKEN') + self.assertIn('already present', str(cm.exception)) + + with no_file('Qconfig.py'), no_envs(), mock_ibmq_provider(): + # Attempt overwriting. + qiskit.wrapper.store_credentials('QISKITRC_TOKEN_2', + overwrite=True) + provider = qiskit.wrapper.register() + + # Ensure that the credentials are the overwritten ones - note that the + # 'hub' parameter was removed. + self.assertEqual(provider._token, 'QISKITRC_TOKEN_2') + self.assertEqual(provider._hub, None) + + def test_environ_over_qiskitrc(self): + """Test order, without qconfig""" + with custom_qiskitrc(): + # Prepare the credentials: both env and qiskitrc present + qiskit.wrapper.store_credentials('QISKITRC_TOKEN') + with no_file('Qconfig.py'), custom_envs({'QE_TOKEN': 'ENVIRON_TOKEN'}): + credentials = discover_credentials() + + self.assertIn('IBMQProvider', credentials) + self.assertEqual(credentials['IBMQProvider']['token'], 'ENVIRON_TOKEN') + + def test_qconfig_over_all(self): + """Test order, with qconfig""" + with custom_qiskitrc(): + # Prepare the credentials: qconfig, env and qiskitrc present + qiskit.wrapper.store_credentials('QISKITRC_TOKEN') + with custom_qconfig(b"APItoken='QCONFIG_TOKEN'"),\ + custom_envs({'QE_TOKEN': 'ENVIRON_TOKEN'}): + credentials = discover_credentials() + + self.assertIn('IBMQProvider', credentials) + self.assertEqual(credentials['IBMQProvider']['token'], 'QCONFIG_TOKEN') + + +# Context managers + +@contextmanager +def no_file(filename): + """Context manager that disallows access to a file.""" + def side_effect(filename_): + """Return False for the specified file.""" + if filename_ == filename: + return False + return isfile_original(filename_) + + # Store the original `os.path.isfile` function, for mocking. + isfile_original = os.path.isfile + patcher = patch('os.path.isfile', side_effect=side_effect) + patcher.start() + yield + patcher.stop() + + +@contextmanager +def no_envs(): + """Context manager that disables qiskit environment variables.""" + # Remove the original variables from `os.environ`. + modified_environ = {key: value for key, value in os.environ.items() + if key not in VARIABLES_MAP.keys()} + patcher = patch.dict(os.environ, modified_environ) + patcher.start() + yield + patcher.stop() + + +@contextmanager +def custom_qiskitrc(contents=b''): + """Context manager that uses a temporary qiskitrc.""" + # Create a temporary file with the contents. + tmp_file = NamedTemporaryFile() + tmp_file.write(contents) + tmp_file.flush() + + # Temporarily modify the default location of the qiskitrc file. + DEFAULT_QISKITRC_FILE_original = _configrc.DEFAULT_QISKITRC_FILE + _configrc.DEFAULT_QISKITRC_FILE = tmp_file.name + yield + + # Delete the temporary file and restore the default location. + tmp_file.close() + _configrc.DEFAULT_QISKITRC_FILE = DEFAULT_QISKITRC_FILE_original + + +@contextmanager +def custom_qconfig(contents=b''): + """Context manager that uses a temporary qconfig.py.""" + # Create a temporary file with the contents. + tmp_file = NamedTemporaryFile(suffix='.py') + tmp_file.write(contents) + tmp_file.flush() + + # Temporarily modify the default location of the qiskitrc file. + DEFAULT_QCONFIG_FILE_original = _qconfig.DEFAULT_QCONFIG_FILE + _qconfig.DEFAULT_QCONFIG_FILE = tmp_file.name + yield + + # Delete the temporary file and restore the default location. + tmp_file.close() + _qconfig.DEFAULT_QCONFIG_FILE = DEFAULT_QCONFIG_FILE_original + + +@contextmanager +def custom_envs(new_environ): + """Context manager that disables qiskit environment variables.""" + # Remove the original variables from `os.environ`. + modified_environ = {**os.environ, **new_environ} + patcher = patch.dict(os.environ, modified_environ) + patcher.start() + yield + patcher.stop() + + +@contextmanager +def mock_ibmq_provider(): + """Mock the initialization of IBMQProvider, so it does not query the api.""" + patcher = patch.object(IBMQProvider, '_authenticate', return_value=None) + patcher2 = patch.object(IBMQProvider, '_discover_remote_backends', return_value={}) + patcher.start() + patcher2.start() + yield + patcher2.stop() + patcher.stop() From 7d30b90f3a701fec67541b79f157a4fc611263de Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Mon, 16 Jul 2018 13:21:51 +0200 Subject: [PATCH 4/6] Update documentation for auto registration --- README.md | 22 ++-- doc/install.rst | 136 +++++++++++++++++------- qiskit/wrapper/_wrapper.py | 8 ++ qiskit/wrapper/credentials/__init__.py | 9 +- qiskit/wrapper/credentials/_settings.py | 11 -- 5 files changed, 123 insertions(+), 63 deletions(-) delete mode 100644 qiskit/wrapper/credentials/_settings.py diff --git a/README.md b/README.md index 100985cc76f1..c5606bf93128 100644 --- a/README.md +++ b/README.md @@ -130,19 +130,23 @@ your IBM Q Experience account: 2. Get an API token from the IBM Q Experience website under _My Account > Advanced > API Token_. This API token allows you to execute your programs with the IBM Q Experience backends. See: [Example](doc/example_real_backend.rst). -3. We are now going to add the necessary credentials to QISKit. Take your token from step 2, here called `MY_API_TOKEN`, and pass it to the `store_credentials` function: +3. We are now going to add the necessary credentials to QISKit. Take your token + from step 2, here called `MY_API_TOKEN`, and pass it to the + `store_credentials` function: - ```python - from qiskit import store_credentials + ```python + from qiskit import store_credentials - store_credentials(`MY_API_TOKEN`) - ``` + store_credentials('MY_API_TOKEN') + ``` 4. If you have access to the IBM Q Network features, you also need to pass the values for your url, hub, group, and project found on your IBM Q account page to `store_credentials`. -Once the credentials are stored, you can register them via +After calling `store_credentials()`, your credentials will be stored into disk. +Once they are stored, you can automatically load and use them in your program +via: ```python from qiskit import register @@ -150,8 +154,10 @@ from qiskit import register register() ``` -For more details on this installation method, using environmental variables, -and the old `Qconfig.py` method, see +For more details on installing Qiskit and for alternative methods for passing +the IBM QX credentials, such as using environment variables, sending them +explicitly and support for the `Qconfig.py` method available in previous +versions, please check [our Qiskit documentation](https://www.qiskit.org/documentation/). diff --git a/doc/install.rst b/doc/install.rst index 4d7a8c203938..c02623664e6a 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -42,63 +42,112 @@ This will install the latest stable release along with all the dependencies. - Get an API token from the IBM Q experience website under “My Account” > “Personal Access Token” -3.1 Store API credentials locally -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -For most users, storing your API credentials is most easily done locally. -Your information is stored locally in a configuration file called `qiskitrc`. +3.1 Automatically loading credentials +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Since Qiskit 0.6, an automatic method that looks for the credentials in several +places can be used for streamlining the setting up of the IBM Q authentication. +This implies that you can set or store your API credentials once after +installation, and when you want to use them, you can simply run: + +.. code:: python + + from qiskit import register + + register() + +This ``register()`` call (without parameters) performs the automatic loading +of the credentials from several sources, and authenticates against IBM Q, +making the online devices available to your program. Please use one of the +following methods for storing the credentials before calling the automatic +registration: + +3.1.1 Store API credentials locally +""""""""""""""""""""""""""""""""""" + +For most users, storing your API credentials is the most convenient approach. +Your information is stored locally in a configuration file called `qiskitrc`, +and once stored, you can use the credentials without explicitly passing them +to your program. + To store your information, simply run: .. code:: python from qiskit import store_credentials - store_credentials(`MY_API_TOKEN`) + store_credentials('MY_API_TOKEN') + where `MY_API_TOKEN` should be replaced with your token. If you are on the IBM Q network, you must also pass `url`, -`hub`, `group`, and `project` arguments to `store_credentials`. +`hub`, `group`, and `project` arguments to `store_credentials`: -To register your credentials with QISKit, simply run: .. code:: python - from qiskit import register - - register() + from qiskit import store_credentials + store_credentials('MY_API_TOKEN', url='http://...', hub='HUB', + group='GROUP', project='PROJECT') -3.2 Load API credentials from environment variables -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +3.1.2 Load API credentials from environment variables +""""""""""""""""""""""""""""""""""""""""""""""""""""" For more advanced users, it is possible to load API credentials from -environmental variables. Specifically, one may set `QE_TOKEN`, -`QE_URL`, `QE_HUB`, `QE_GROUP`, and `QE_PROJECT`. These can then be registered -with QISKit: +environment variables. Specifically, you can set the following environment +variables: -.. code:: python +* `QE_TOKEN`, +* `QE_URL` +* `QE_HUB` +* `QE_GROUP` +* `QE_PROJECT`. - from qiskit import register +Note that if they are present in your environment, they will take precedence +over the credentials stored in disk. - register() +3.1.3 Load API credentials from Qconfig.py +"""""""""""""""""""""""""""""""""""""""""" + +For compatibility with configurations set for Qiskit versions earlier than 0.6, +the credentials can also be stored in a file called ``Qconfig.py`` placed in +the directory where your program is invoked from. For convenience, we provide +a default version of this file you can use as a reference - using your favorite +editor, create a ``Qconfig.py`` file in the folder of your program with the +following contents: -3.3 Load API credentials from Qconfig.py -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. code:: python -The API token can be loaded from a file called -``Qconfig.py``. For convenience, we provide a default version of -this file that you can use as a reference: `Qconfig.py.default`_. -After downloading that file, copy it into the folder where you -will be invoking the SDK (on Windows, replace ``cp`` with ``copy``): + APItoken = 'PUT_YOUR_API_TOKEN_HERE' -.. code:: sh + config = { + 'url': 'https://quantumexperience.ng.bluemix.net/api', - cp Qconfig.py.default Qconfig.py + # If you have access to IBM Q features, you also need to fill the "hub", + # "group", and "project" details. Replace "None" on the lines below + # with your details from Quantum Experience, quoting the strings, for + # example: 'hub': 'my_hub' + # You will also need to update the 'url' above, pointing it to your custom + # URL for IBM Q. + 'hub': None, + 'group': None, + 'project': None + } + + if 'APItoken' not in locals(): + raise Exception('Please set up your access token. See Qconfig.py.') -Open your ``Qconfig.py``, remove the ``#`` from the beginning of the API -token line, and copy/paste your API token into the space between the -quotation marks on that line. Save and close the file. +And customize the following lines: + +* copy/paste your API token into the space between the quotation marks on the + first line (``APItoken = 'PUT_YOUR_API_TOKEN_HERE``). +* if you have access to the IBM Q features, you also need to setup the + values for your url, hub, group, and project. You can do so by filling the + ``config`` variable with the values you can find on your IBM Q account + page. For example, a valid and fully configured ``Qconfig.py`` file would look like: @@ -110,13 +159,8 @@ For example, a valid and fully configured ``Qconfig.py`` file would look like: 'url': 'https://quantumexperience.ng.bluemix.net/api' } -If you have access to the IBM Q features, you also need to setup the -values for your hub, group, and project. You can do so by filling the -``config`` variable with the values you can find on your IBM Q account -page. - -For example, a valid and fully configured ``Qconfig.py`` file for IBM Q -users would look like: +For IBM Q users, a valid and fully configured ``Qconfig.py`` file would look +like: .. code:: python @@ -130,14 +174,26 @@ users would look like: 'project': 'MY_PROJECT' } -If the `Qconfig.py` is in the current working directory, then it can be -automatrically registered with QISKit: +Note that if a ``Qconfig.py`` file is present in your directory, it will take +precedence over the environment variables or the credentials stored in disk. + +3.2 Manually loading credentials +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In more complex scenarios or for users that need finer control over multiple +accounts, please note that you can pass the API token and the other parameters +directly to the ``register()`` function, which will ignore the automatic +loading of the credentials and use the arguments directly. For example:: .. code:: python from qiskit import register - register() + register('MY_API_TOKEN', url='https://my.url') + +will try to authenticate using ``MY_API_TOKEN`` and the specified URL, +regardless of the configuration stored in the config file, the environment +variables, or the ``Qconfig.py`` file, if any. Install Jupyter-based tutorials =============================== diff --git a/qiskit/wrapper/_wrapper.py b/qiskit/wrapper/_wrapper.py index 920c0f17415f..954637f9809a 100644 --- a/qiskit/wrapper/_wrapper.py +++ b/qiskit/wrapper/_wrapper.py @@ -29,6 +29,14 @@ def register(*args, provider_class=IBMQProvider, **kwargs): Authenticate against an online backend provider. This is a factory method that returns the provider that gets registered. + Note that if no parameters are passed, this method will try to + automatically discover the credentials for IBMQ in the following places, + in order:: + + 1. in the `Qconfig.py` file in the current working directory. + 2. in the environment variables. + 3. in the `qiskitrc` configuration file. + Args: args (tuple): positional arguments passed to provider class initialization provider_class (BaseProvider): provider class diff --git a/qiskit/wrapper/credentials/__init__.py b/qiskit/wrapper/credentials/__init__.py index 19b7cad61aba..ca205e43d804 100644 --- a/qiskit/wrapper/credentials/__init__.py +++ b/qiskit/wrapper/credentials/__init__.py @@ -24,10 +24,11 @@ def discover_credentials(): Automatically discover credentials for online providers. This method looks for credentials in the following locations, in order, - and returning as soon as credentials are found: - 1. in the `Qconfig.py` file in the current working directory. - 2. in the environment variables. - 3. in the `qiskitrc` configuration file. + and returning as soon as credentials are found:: + + 1. in the `Qconfig.py` file in the current working directory. + 2. in the environment variables. + 3. in the `qiskitrc` configuration file. Returns: dict: dictionary with the contents of the configuration file, with diff --git a/qiskit/wrapper/credentials/_settings.py b/qiskit/wrapper/credentials/_settings.py deleted file mode 100644 index 652158fab627..000000000000 --- a/qiskit/wrapper/credentials/_settings.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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. - -"""Module that holds the runtime location of qiskitrc file. -""" - -REGISTER_CALLED = 0 From 8e1ca5703fad5fea800b95379aabc41417f4737a Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Tue, 17 Jul 2018 17:28:00 +0200 Subject: [PATCH 5/6] Add changelog entry --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index eaf4f713f07a..5ae4e2aa5805 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,8 @@ The format is based on `Keep a Changelog`_. Added ----- +- Introduced new options for handling credentials (qiskitrc file, environment + variables) and automatic registration. (#547) Changed ------- From 46286a65e644916cec07d2b07c35c43a1a0449dc Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Fri, 20 Jul 2018 11:17:17 +0200 Subject: [PATCH 6/6] Use full class name for account name, spelling Fixes after review: * use the full class name (module + class) for the account name, in order to ensure it is unique, moving the calculation to its own function for reusability. * spelling and other fixes. --- README.md | 2 +- doc/install.rst | 2 +- qiskit/wrapper/_wrapper.py | 3 ++- qiskit/wrapper/credentials/__init__.py | 1 + qiskit/wrapper/credentials/_configrc.py | 12 ++++++++---- qiskit/wrapper/credentials/_environ.py | 4 ++-- qiskit/wrapper/credentials/_qconfig.py | 3 ++- qiskit/wrapper/credentials/_utils.py | 25 +++++++++++++++++++++++++ test/python/common.py | 10 ++++++---- test/python/test_registration.py | 17 +++++++++++------ 10 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 qiskit/wrapper/credentials/_utils.py diff --git a/README.md b/README.md index c5606bf93128..992bf10ca1aa 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ your IBM Q Experience account: page to `store_credentials`. After calling `store_credentials()`, your credentials will be stored into disk. -Once they are stored, you can automatically load and use them in your program +Once they are stored, Qiskit will automatically load and use them in your program via: ```python diff --git a/doc/install.rst b/doc/install.rst index c02623664e6a..c5969ba79156 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -143,7 +143,7 @@ following contents: And customize the following lines: * copy/paste your API token into the space between the quotation marks on the - first line (``APItoken = 'PUT_YOUR_API_TOKEN_HERE``). + first line (``APItoken = 'PUT_YOUR_API_TOKEN_HERE'``). * if you have access to the IBM Q features, you also need to setup the values for your url, hub, group, and project. You can do so by filling the ``config`` variable with the values you can find on your IBM Q account diff --git a/qiskit/wrapper/_wrapper.py b/qiskit/wrapper/_wrapper.py index 954637f9809a..78fdcb21268a 100644 --- a/qiskit/wrapper/_wrapper.py +++ b/qiskit/wrapper/_wrapper.py @@ -63,7 +63,8 @@ def register(*args, provider_class=IBMQProvider, **kwargs): """ # Try to autodiscover credentials if not passed. if not args and not kwargs and provider_class == IBMQProvider: - kwargs = credentials.discover_credentials().get(IBMQProvider.__name__) or {} + kwargs = credentials.discover_credentials().get( + credentials.get_account_name(IBMQProvider)) or {} if not kwargs: raise QISKitError( 'No IBMQ credentials found. Please pass them explicitly or ' diff --git a/qiskit/wrapper/credentials/__init__.py b/qiskit/wrapper/credentials/__init__.py index ca205e43d804..3028bcc673e4 100644 --- a/qiskit/wrapper/credentials/__init__.py +++ b/qiskit/wrapper/credentials/__init__.py @@ -14,6 +14,7 @@ from ._configrc import read_credentials_from_qiskitrc, store_credentials from ._environ import read_credentials_from_environ from ._qconfig import read_credentials_from_qconfig +from ._utils import get_account_name logger = logging.getLogger(__name__) diff --git a/qiskit/wrapper/credentials/_configrc.py b/qiskit/wrapper/credentials/_configrc.py index ea1b68fd973b..4427124f2829 100644 --- a/qiskit/wrapper/credentials/_configrc.py +++ b/qiskit/wrapper/credentials/_configrc.py @@ -15,6 +15,7 @@ from qiskit import QISKitError from qiskit.backends.ibmq import IBMQProvider +from ._utils import get_account_name DEFAULT_QISKITRC_FILE = os.path.join(os.path.expanduser("~"), @@ -51,11 +52,14 @@ def read_credentials_from_qiskitrc(filename=None): credentials_dict = {} for name in config_parser.sections(): single_credentials = dict(config_parser.items(name)) - # TODO: 'proxies' is the only value that is a dict. Consider moving to - # json configuration or splitting into single keys manually. + # Individually convert keys to their right types. + # TODO: consider generalizing, moving to json configuration or a more + # robust alternative. if 'proxies' in single_credentials.keys(): single_credentials['proxies'] = literal_eval( single_credentials['proxies']) + if 'verify' in single_credentials.keys(): + single_credentials['verify'] = bool(single_credentials['verify']) credentials_dict[name] = single_credentials return credentials_dict @@ -100,7 +104,7 @@ def store_credentials(provider_class=IBMQProvider, overwrite=False, the account_name could not be assigned. """ # Set the name of the Provider from the class. - account_name = provider_class.__name__ + account_name = get_account_name(provider_class) # Read the current providers stored in the configuration file. filename = filename or DEFAULT_QISKITRC_FILE credentials = read_credentials_from_qiskitrc(filename) @@ -127,7 +131,7 @@ def remove_credentials(provider_class=IBMQProvider, filename=None): file. """ # Set the name of the Provider from the class. - account_name = provider_class.__name__ + account_name = get_account_name(provider_class) credentials = read_credentials_from_qiskitrc(filename) try: diff --git a/qiskit/wrapper/credentials/_environ.py b/qiskit/wrapper/credentials/_environ.py index c344f3d21445..9f22e32ac093 100644 --- a/qiskit/wrapper/credentials/_environ.py +++ b/qiskit/wrapper/credentials/_environ.py @@ -12,7 +12,7 @@ import os from qiskit.backends.ibmq import IBMQProvider - +from ._utils import get_account_name # Dictionary that maps `ENV_VARIABLE_NAME` to credential parameter. VARIABLES_MAP = { @@ -44,4 +44,4 @@ def read_credentials_from_environ(): if os.getenv(envar_name): credentials[credential_key] = os.getenv(envar_name) - return {IBMQProvider.__name__: credentials} + return {get_account_name(IBMQProvider): credentials} diff --git a/qiskit/wrapper/credentials/_qconfig.py b/qiskit/wrapper/credentials/_qconfig.py index 6e67c4163466..0951529d0a59 100644 --- a/qiskit/wrapper/credentials/_qconfig.py +++ b/qiskit/wrapper/credentials/_qconfig.py @@ -14,6 +14,7 @@ from qiskit import QISKitError from qiskit.backends.ibmq import IBMQProvider +from ._utils import get_account_name DEFAULT_QCONFIG_FILE = 'Qconfig.py' @@ -59,4 +60,4 @@ def read_credentials_from_qconfig(): # pylint: disable=broad-except raise QISKitError('Error loading Qconfig.py: %s' % str(ex)) - return {IBMQProvider.__name__: credentials} + return {get_account_name(IBMQProvider): credentials} diff --git a/qiskit/wrapper/credentials/_utils.py b/qiskit/wrapper/credentials/_utils.py new file mode 100644 index 000000000000..17ad2d84b461 --- /dev/null +++ b/qiskit/wrapper/credentials/_utils.py @@ -0,0 +1,25 @@ +# -*- 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 credentials. +""" + + +def get_account_name(provider_class): + """ + Return the account name for a particular provider. This name is used by + Qiskit internally and in the configuration file and uniquely identifies + a provider. + + Args: + provider_class (class): class for the account. + + Returns: + str: the account name. + """ + return '{}.{}'.format(provider_class.__module__, provider_class.__name__) diff --git a/test/python/common.py b/test/python/common.py index 60f8c7fa4fdc..cd6b8d746204 100644 --- a/test/python/common.py +++ b/test/python/common.py @@ -15,7 +15,8 @@ import unittest from unittest.util import safe_repr from qiskit import __path__ as qiskit_path -from qiskit.wrapper.credentials import discover_credentials +from qiskit.backends.ibmq import IBMQProvider +from qiskit.wrapper.credentials import discover_credentials, get_account_name from qiskit.wrapper.defaultqiskitprovider import DefaultQISKitProvider @@ -249,9 +250,10 @@ def _(*args, **kwargs): _wrapper._DEFAULT_PROVIDER = DefaultQISKitProvider() # Attempt to read the standard credentials. + account_name = get_account_name(IBMQProvider) discovered_credentials = discover_credentials() - try: - credentials = next(iter(discovered_credentials.values())) + if account_name in discovered_credentials.keys(): + credentials = discovered_credentials[account_name] kwargs.update({ 'QE_TOKEN': credentials.get('token'), 'QE_URL': credentials.get('url'), @@ -259,7 +261,7 @@ def _(*args, **kwargs): 'group': credentials.get('group'), 'project': credentials.get('project'), }) - except StopIteration: + else: raise Exception('Could not locate valid credentials') return func(*args, **kwargs) diff --git a/test/python/test_registration.py b/test/python/test_registration.py index 8b597bc139bb..c121111677f9 100644 --- a/test/python/test_registration.py +++ b/test/python/test_registration.py @@ -19,15 +19,20 @@ import qiskit from qiskit import QISKitError from qiskit.backends.ibmq import IBMQProvider -from qiskit.wrapper.credentials import _configrc, _qconfig, discover_credentials +from qiskit.wrapper.credentials import (_configrc, _qconfig, + discover_credentials, get_account_name) from qiskit.wrapper.credentials._environ import VARIABLES_MAP from .common import QiskitTestCase # TODO: NamedTemporaryFiles do not support name in Windows @skipIf(os.name == 'nt', 'Test not supported in Windows') -class TestWrapperCredentuals(QiskitTestCase): +class TestWrapperCredentials(QiskitTestCase): """Wrapper autoregistration and credentials test case.""" + def setUp(self): + super(TestWrapperCredentials, self).setUp() + self.ibmq_account_name = get_account_name(IBMQProvider) + def test_autoregister_no_credentials(self): """Test register() with no credentials available.""" with no_file('Qconfig.py'), no_file(_configrc.DEFAULT_QISKITRC_FILE), no_envs(): @@ -73,8 +78,8 @@ def test_environ_over_qiskitrc(self): with no_file('Qconfig.py'), custom_envs({'QE_TOKEN': 'ENVIRON_TOKEN'}): credentials = discover_credentials() - self.assertIn('IBMQProvider', credentials) - self.assertEqual(credentials['IBMQProvider']['token'], 'ENVIRON_TOKEN') + self.assertIn(self.ibmq_account_name, credentials) + self.assertEqual(credentials[self.ibmq_account_name]['token'], 'ENVIRON_TOKEN') def test_qconfig_over_all(self): """Test order, with qconfig""" @@ -85,8 +90,8 @@ def test_qconfig_over_all(self): custom_envs({'QE_TOKEN': 'ENVIRON_TOKEN'}): credentials = discover_credentials() - self.assertIn('IBMQProvider', credentials) - self.assertEqual(credentials['IBMQProvider']['token'], 'QCONFIG_TOKEN') + self.assertIn(self.ibmq_account_name, credentials) + self.assertEqual(credentials[self.ibmq_account_name]['token'], 'QCONFIG_TOKEN') # Context managers