-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve user authentication handling #547
Conversation
qiskit/wrapper/configrc/_settings.py
Outdated
# limitations under the License. | ||
# ============================================================================= | ||
|
||
QISKIT_RC_FILE = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this file only for internal use or is it meant to be edited by a user?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internal only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It holds the location of the qiskitrc file.
Currently there is a function in the wrapper that will register any provider information found in the
To get credentials into the |
For the new files, can you adjust the license header so it follows the format recently introduced by #550 ?
|
Dropped 'make generating Qconfig.py easier' from the scope as our goal is to move away from them. |
@diego-plan9 , the online tests are not running for this PR. In order to test the API registration functions, I need to run the tests in CI only, otherwise I would kill the credentials on the users local machine. |
qiskit/wrapper/_wrapper.py
Outdated
""" | ||
_registered_names = [p.name for p in _DEFAULT_PROVIDER.providers] | ||
if provider_name is None: | ||
return _registered_names |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why return all the _registered_names
if nothing is passed as provider_name
?
shouldn't you make the argument mandatory, since otherwise it doesn't make sense to call unregister
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I didn't want to make another function available_providers
just to get the list to pass to unregister, so I did this. Perhaps I should just make the new function. In principle, it is possible for credentials to come from multiple locations, and they would not show up as stored credentials.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turned out to be a lengthy review - but the summary is that functionality-wise is looking great and covers the goals very nicely (in particular, thanks a lot for the effort in the documentation), and that with a bit more focus on the modularity and architecture aspects it will be even nicer! As a matter of fact, nearly all the comments are geared towards simplifying the implementation and "moving things around a bit" in the hopes of achieving a cleaner implementation that breaks it into finer pieces and fits better into the core - looking forward to the next round!
There is also the issue of the tests - I did not review them yet following the mention that they were a bit WIP at the moment, but I'm confident we can come up with a solution that allows them to run locally eventually.
qiskit/wrapper/configrc/_configrc.py
Outdated
import ast | ||
import configparser | ||
from qiskit import QISKitError | ||
from qiskit.wrapper.configrc import _settings as rc_set |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we please avoid import xxx as yyy
in general, for readability?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QISKit already uses the following:
import numpy as np
import scipy.linalg as la
import scipy.sparse as sp
import scipy.sparse.csgraph as cs
import networkx as nx
from sympy import Number as N
from qiskit.qasm import _node as node
import ply.lex as lex
from . import _node as node
import ply.yacc as yacc
import matplotlib.pyplot as plt
why is my usage of the same functionality problematic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, for readability - in general they do not provide tangible benefits, but have the drawback add another level of indirection in the code, which hinders parsing the code at a glance. The examples you list are either references to other libraries (where some of them, as the numpy alias, are relatively widespread) with only three of them related to qiskit
modules (and part of code that has not changed much since the beginning of the project).
qiskit/wrapper/configrc/_configrc.py
Outdated
# the LICENSE.txt file in the root directory of this source tree. | ||
|
||
""" | ||
A module for generating and manipulating the qiskitrc file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add to the documentation the format of the .qiskitrc
file (somewhere in the sphinx doc - I'm unsure about the best place, but as long as it is there we can move it around)?
And on that topic, can we rethink the format itself? It seems that currently it is allowing dicts and evaluating the contents back when needed, which might be not too well suited to our use case. What about simplifying it to just one value per key, as in:
[ibmq]
token=123456
url=http://foo
[somethingelse]
token=5678
url=http://bar
group=x
...
Since we don't really have much more use for the sections in the .qiskitrc currently, it would make it more straightforward when editing manually, and at the same time probably allow a cleaner and safer implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally did that but it is not very clean, and the minute we add other stuff to the file we run into trouble. Since we can store a dict, that seemed to be the cleanest way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is preferable to have a default .qiskitrc
file with a template discussing the options and syntax rather than forcing the user to take a look at the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I do not want them to see the file. I just want them to use the provided functions to register.
qiskit/wrapper/configrc/_configrc.py
Outdated
|
||
""" | ||
A module for generating and manipulating the qiskitrc file. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regardless of the configuration format, can we refactor this module to make it more pythonic, and simplify its usage - ideally be able to something like:
> credentials = read_credentials_from_file()
{
'acount1': {'token': 12345, 'url': 'http://fo'},
...
}
> credentials['account1']['token'] = 'mynewtoken'
> write_credentials_to_file(credentials)
or even go a bit further and have a proper credentials class. This way we can centralize the access to the qiskitrc file, and removing, updating, and checking credentials becomes simply modifiying the dict/class - for the intermediate functions (if needed) between reading and writing, also passing ConfigParser
around would probably be a better approach than having to open and close the config file in several places.
This would also help for the environment variables case: if the function that checks for the environment variables returns the same structure, switching between them becomes simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please note as well that the names (and the structure) used in the review as a whole are tentative - better names than the ones sketched on the comments are needed ™️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can already do what you are asking:
creds = get_credentials('ibmq')
creds['token'] = 'MY_NEW_TOKEN'
store_credentials(**creds, overwrite=True)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems it cannot be done at the granularity level that was suggested in the comment, which was the important bit of it.
qiskit/__init__.py
Outdated
|
||
# Import the wrapper, to make it available when doing "import qiskit". | ||
from . import wrapper | ||
|
||
from .wrapper.configrc._configrc import (has_qiskit_configrc, get_credentials, | ||
store_credentials, remove_credentials) | ||
from .wrapper._wrapper import qiskitrc_register_providers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you re-check if all these functions are really meant to be invoked directly by the end user? We should be careful about not adding too many things to the root qiskit
namespace unless really needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A good point, let me see what we really need to expose for average user.
qiskit/wrapper/_wrapper.py
Outdated
@@ -21,35 +27,159 @@ | |||
_DEFAULT_PROVIDER = DefaultQISKitProvider() | |||
|
|||
|
|||
def register(token, url='https://quantumexperience.ng.bluemix.net/api', | |||
def register(token=None, url='https://quantumexperience.ng.bluemix.net/api', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we refactor a bit this function as well, aiming for simplifying the original use case of the function (ie. the current functionality) and treat the case of a missing token in a more modularized form? Similar to:
def register(token=None, ...):
if not token:
token, url, hub, ... = _load_token(...)
provider = provider(token, ...)
def _load_token(....):
# try _load_token_from_qconfig(), _load_token_from_envs(), _load_token_from_qiskitrc()
The idea is again to try to better encapsulate the functionality and the complexity (perhaps even moving the token-finding-in-all-places functionality as a whole to the wrapper.configrc
module, renaming in a more generic form).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it can be cleaned up. I started adding functionality a bit hastily at the end.
qiskit/wrapper/configrc/_settings.py
Outdated
|
||
QISKIT_RC_FILE = None | ||
|
||
REGISTER_CALLED = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to make better use of qiskit-core/qiskit/wrapper/defaultqiskitprovider.py
for keeping track of the registered backends and their names, instead of a global variable? Currently it uses a list of providers, but by extending it to a dict it seems it would simplify the handling of the providers names and split the functionality better - with the big benefit of having a single instance where that information is stored instead of global scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _settings
does not keep track of the registered backends, it only is used for storing runtime variables. The backends are stored as usual, using the new Provider.name
functionality to understand which are which.
qiskit/wrapper/configrc/_settings.py
Outdated
"""Module that holds the runtime location of qiskitrc file. | ||
""" | ||
|
||
QISKIT_RC_FILE = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this variable contain a value other than ~/.qiskit/qiskitrc
? In the usual spirit, if we can avoid having a global variable, other implementation would be preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be None
. It is used not only to point to the qiskitrc
file, but to tell the routines if it can not be found. It is set from None
to the location of the qiskitrc
file at runtime if it is there.
qiskit/backends/baseprovider.py
Outdated
@@ -17,7 +17,7 @@ class BaseProvider(ABC): | |||
Base class for a backend provider. | |||
""" | |||
def __init__(self, *args, **kwargs): | |||
pass | |||
self.name = 'base' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we re-think the need for having a provider name
as a required instance attribute? It seems the main reason for introducing the parameter is for helping with the changes of the implementation of register()
, and by design the Base
classes should ideally be as minimal as possible to avoid side effects and constraints - please see the note in IBMQProvider
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could, but then it would make other code more complicated as one would have to check for a provider name, or whether it even exists or not, and what to do in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is preferable to keep the complexity outside the core to not increase coupling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you suggest doing if there are multiple IBMQ providers to register/unregister? I think people are fairly comfortable with the idea of a provider name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The solution depends on the specific use-case. For instance:
- If we don't allow several instances of the same provider, simply talking about them is wrong. There is no identity problem and you can always query
type(provider)
. - If we do allow several instances of the same provider, then they can be stored in a dictionary as @diego-plan9 suggested you:
{ "name": provider_instance }
.
...
So, would you mind to clarify why do you need names
so we can come up with the proper solution both meeting your needs and without introducing complexity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole point of the name
is that we may have multiple IBMQProvider
instances. It also simplifies iterating through the list of registered providers and simplifies the unregistration process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, use @diego-plan9's name dictionary strategy instead. The DefaultQISKitProvider
which is defined as a "meta provider" is intended to add extra functionality like the one you're suggesting. This solution would meet your API usage expectation without introducing extra complexity to the core.
qiskit/backends/ibmq/ibmqprovider.py
Outdated
elif 'q-console' in url: | ||
provider_name = 'qnet' | ||
else: | ||
raise QISKitError('Unkown IBMQ provider.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following up on the comment in BaseProvider
, I think this introduces some tiny yet undesirable side effects: in particular, makes provider_name
to be effectively required when creating an IBMQProvider
instance if using non-standard URLs without real benefits when using the provider directly - ie. the .name
is only relevant for register()
and their related functions, which compromises a bit the decoupling. It also seems that in this revision, it is possible for two instances to have the same name
, which I'm not sure if it is by design and would require some clarification (ie. what is the relation between a provider and its name - always the same name, as happens with the LocalProvider
, a default name but potentially dependent on the user needs, as in this one, etc?).
Instead of that - what about delegating the handling of provider names to DefaultQISKitProvider
? It is arguably the purpose of that class, and it seems that by using a "dict of provider_name: Provider" there would be enough to cover the needs introduced by this PR and at the same time avoid introducing application-dependent changes in the base classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name is indeed used only to register and unregister at the moment. However, as discussed in our meeting, there could be cases where there are multiple IBMQProvider
instances registered, and I need a way to address them, e.g. to unregister/register one and not the other. I demoed one such example of this.
If you can think of a better way to accomplish the same task, then I am curious to hear your suggestion.
qiskit/wrapper/_wrapper.py
Outdated
QISKitError: if the provider name is not recognized. | ||
""" | ||
if provider_name == 'core': | ||
raise QISKitError("Cannot unregister 'core' provider.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very random thought: why do we want to restrict unregistering the core
provider?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we can not register it, and what would be the use case to remove the 'core' solvers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nonhermitian the ideas in this PR are good and seems to simplify the registration process. Nevertheless, I don't see the point in adding name
to the local and IBM Q providers. What is the use-case for that new functionality?
`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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be convenient to link IBM Q account
with the actual URL and a complete example of how to pass these extra parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same text that was there before, and is only of interest to hub members.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking although It is never late for clarifying the docs.
doc/install.rst
Outdated
|
||
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 registed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace registed
with registered
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good find.
|
||
from qiskit import register | ||
|
||
register() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documenting the new way of registering by providing three sections with exactly the same example can be misleading. The user can think there is an error in the example or something magic is happening behind the scenes. My recommendation is to introduce register()
first saying it is an automatic method which will look for configuration in several origins according to an order, then introduce the origins in separate sections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
qiskit/wrapper/_register.py
Outdated
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can cause security issues, let's name it more explicitly such as ssl_verification
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That exact same text comes from the old register function.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this is just the old text.
qiskit/_quantumprogram.py
Outdated
@@ -630,7 +630,7 @@ def get_initial_circuit(self): | |||
############################################################### | |||
|
|||
def set_api(self, token, url, hub=None, group=None, project=None, | |||
proxies=None, verify=True): | |||
proxies=None, verify=True, provider_name='ibmq'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QuantumProgram
instances should be agnostic of providers and so, we should not provide a default value for the provider_name
parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe I forgot to remove this in the latest version. Will remove.
test/python/test_quantumprogram.py
Outdated
) | ||
# SDK will throw ConnectionError on every call that implies a connection | ||
self.assertRaises(ConnectionError, qp.set_api, FAKE_TOKEN, FAKE_URL) | ||
# def test_offline(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove dead code or disable the test explicitly with unittest.skip
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True.
qiskit/wrapper/configrc/_configrc.py
Outdated
# the LICENSE.txt file in the root directory of this source tree. | ||
|
||
""" | ||
A module for generating and manipulating the qiskitrc file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is preferable to have a default .qiskitrc
file with a template discussing the options and syntax rather than forcing the user to take a look at the code.
qiskit/wrapper/configrc/_settings.py
Outdated
"""Module that holds the runtime location of qiskitrc file. | ||
""" | ||
|
||
QISKIT_RC_FILE = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this is not a setting but shared state for the functions in _configrc.py
. I'm not a friend of using OOP when it is not necessary, so a module works form me. This variable should be in _configrc.py
as a module-level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not want the user to see the qiskitrc file at all. I want them to simply pass a token and possibly url to the store_credentials function, or register.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this reply is misplaced but it is fair.
qiskit/wrapper/configrc/_settings.py
Outdated
|
||
QISKIT_RC_FILE = None | ||
|
||
REGISTER_CALLED = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, this is related to _register.py
only and so, it should live there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QISKIT_RC_FILE is used in several places. The other one can be moved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually REGISTER_CALLED is also used in three different modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to my search results of your branch, the name REGISTER_CALLED
appears indeed in two different places (in addition to this file): _register.py
and _wrapper.py
whereas QISKIT_RC_FILE
appears only in _settings.py
. So, you can move QISKIT_RC_FILE
there.
The problem with REGISTER_CALLED
arises from the need for communicating between _wrapper.py
and _register.py
. This is one solution:
- Move the registration code in
_wrapper.py
into another function_register_using_origin_chain
. And makeregister
function to delegate into this new function. - Move this function inside
_register.py
and adjust the imports in_wrapper.py
.
Now all REGISTER_CALLED
only appears in _register.py
and you can turn the global variable into a module-level one inside _register.py
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do this. It makes the old register
function just a wrapper for one inside of _register
but I am fine with that.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this is just the old text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Paul,
Please, address the requested changes and clarify a couple of matters:
- The need of
name
so we can come up with an alternative not increasing the complexity of core classes. - The use-case of
unregister
.
`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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking although It is never late for clarifying the docs.
doc/install.rst
Outdated
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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- What to do if multiple IBMQProviders are created. 2) How best to search through multiple registered providers [LocalProvider, IBMQProvider1, IBMQProvider2, ExternalProvider1,...] so that double registration can not happen, and we know which providers are loaded so that one or more may be unregistered. Unregister was requested elsewhere.
qiskit/backends/baseprovider.py
Outdated
@@ -17,7 +17,7 @@ class BaseProvider(ABC): | |||
Base class for a backend provider. | |||
""" | |||
def __init__(self, *args, **kwargs): | |||
pass | |||
self.name = 'base' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The solution depends on the specific use-case. For instance:
- If we don't allow several instances of the same provider, simply talking about them is wrong. There is no identity problem and you can always query
type(provider)
. - If we do allow several instances of the same provider, then they can be stored in a dictionary as @diego-plan9 suggested you:
{ "name": provider_instance }
.
...
So, would you mind to clarify why do you need names
so we can come up with the proper solution both meeting your needs and without introducing complexity?
qiskit/wrapper/configrc/_settings.py
Outdated
"""Module that holds the runtime location of qiskitrc file. | ||
""" | ||
|
||
QISKIT_RC_FILE = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this reply is misplaced but it is fair.
@delapuente please fork my branch and make the stylistic changes you see fit. Also, @diego-plan9, as we were both assigned to this registration update task, please fork my branch, and make any changes you think need to be done. |
I'll go for it, as there are a number of issues still to be addressed. In the meantime, can you update on the current state of the tests - do you have a proposal or an implementation on how to get them back into shape? |
Can we add one line to the CHANGELOG for this? |
Labeling as |
After discussion in private last week, I have proceeded retaking this PR and doing a bit of cleanup, as it had diverged quite a lot in regards to the recent changes (the In the hopes of being able to include the PR in 0.6, I went ahead and narrowed down the scope of this PR even further: the current plan is to "just" have a replacement of In b09e794b8b70769c5262776dffe6ac5b45f303cb some basic tests were added for the core functionality. I have taken off the |
Taking the PR out of WIP, after tweaking up a bit the documentation to account for the latest changes and for the comments in the initial review . |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, it looks OK but I strongly encourage using module + class name to identify the providers.
There are some typos we should fix before merging, though.
```python | ||
APItoken = 'MY_API_TOKEN' | ||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: it seems we are using soft wrapping so there is no need of manually breaking the lines. I think we should be consistent here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we are not really using soft-wrapping in most of the README.md
- and technically using line wrapping is more in line with the rest of the file (if I recall correctly, we did have full hard-wrapping for the file at one point in time, but lots of changes were made that resulted in the current mix). I'd love indeed to have it fully consistent one way or the other eventually, but seems out of the scope of this PR.
|
||
4. Substitute `MY_API_TOKEN` with your real API Token extracted in step 2. | ||
store_credentials('MY_API_TOKEN') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we say "you don't need to do this more than once"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I would expect that the reader knows that the steps in this whole section "Configure you API token" is to be done once (create account, create token, set it up, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw this explained later, it is fine.
README.md
Outdated
|
||
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: | ||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would replace you can automatically load
with QISKit will automatically load
to highlight this is automatic.
doc/install.rst
Outdated
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``). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a single quotation '
is missing after PUT_YOUR_API_TOKEN_HERE
. Isn't it?
qiskit/wrapper/_wrapper.py
Outdated
""" | ||
# 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 {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call an identity(klass)
function that encapsulates the calculus of the provider name?
test/python/test_registration.py
Outdated
@skipIf(os.name == 'nt', 'Test not supported in Windows') | ||
class TestWrapperCredentuals(QiskitTestCase): | ||
"""Wrapper autoregistration and credentials test case.""" | ||
def test_autoregister_no_credentials(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a blank line before the function?
with self.assertRaises(QISKitError) as cm: | ||
qiskit.wrapper.register() | ||
|
||
self.assertIn('No IBMQ credentials found', str(cm.exception)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: I feel asserting on exception messages is brittle and I would prefer to provide a more specific exception class.
self.assertEqual(provider._proxies, {'http': 'foo'}) | ||
|
||
def test_store_credentials_overwrite(self): | ||
"""Test overwritind qiskitrc credentials.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: replace overwritind
with overwriting
.
self.assertEqual(provider._token, 'QISKITRC_TOKEN') | ||
self.assertEqual(provider._proxies, {'http': 'foo'}) | ||
|
||
def test_store_credentials_overwrite(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would break this test in several, one testing overwriting fails, another for testing failure implies no data is overwritten.
@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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to ignore this but you can simplify this pattern with:
@contextmanager
def mock_ibmq_provider():
with patch.object(IBMQProvider, '_authenticate', return_value=None), path.object(IBMQProvider, '_discover_remote_backends', return_value={}):
yield
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
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.
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.
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.
Thanks for the review, @delapuente ! I have addressed most of the concerns, focusing on the main one (the use of the full qualified name of the class as the account name) and the unusual amount of typos, leaving some of the comments for future iterations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Diego, it looks great to me. Just the comment about the error but it's non-blocking. Nice work.
|
||
4. Substitute `MY_API_TOKEN` with your real API Token extracted in step 2. | ||
store_credentials('MY_API_TOKEN') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw this explained later, it is fine.
kwargs['group'] = QE_GROUP | ||
kwargs['project'] = QE_PROJECT | ||
# Cleanup the credentials, as this file is shared by the tests. | ||
from qiskit.wrapper import _wrapper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: We should not have production code depending on tests, I've opened #669 for addressing this.
'project': credentials.get('project'), | ||
}) | ||
else: | ||
raise Exception('Could not locate valid credentials') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not it be a QISKitError
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I don't think so, since this exception raised in the test suite (actually before the tests), so the usual conventions for the qiskit
module do not really apply.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree.
Improve user authentication handling
Description
Work to make changes inline with #540.
qiskitrc
file.Qconfig.py
if in local dir.Summary of Changes Made
qiskitrc
file in the usersHOME/.qiskit
folder.register()
with no arguments loads credentials from aQconfig.py
file in thecwd
, the env varsQE_TOKEN
,QE_URL
,QE_HUB
,QE_GROUP
, andQE_PROJECT
, then finallyqiskitrc
, in that order.qiskitrc
file usingstore
,get
, andremove
_credentials
functions.ibmq
, where as the Q network provider is calledqnet
.unregister()
function.register(provider_name='MY_PROVIDER')
.requires_ci
test decorator for testing registration routines that would break a local users registration.Motivation and Context
Make the end users life easier for registering providers.
How Has This Been Tested?
Screenshots (if appropriate):
Types of changes
Checklist: