azure-quantum 1.1.1__py3-none-any.whl → 1.1.2.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- azure/quantum/_authentication/_chained.py +21 -23
- azure/quantum/_authentication/_default.py +97 -159
- azure/quantum/_authentication/_token.py +41 -38
- azure/quantum/_client/_client.py +35 -9
- azure/quantum/_client/_configuration.py +12 -12
- azure/quantum/_client/_serialization.py +46 -37
- azure/quantum/_client/_version.py +1 -1
- azure/quantum/_client/models/_models.py +8 -8
- azure/quantum/_client/operations/_operations.py +261 -167
- azure/quantum/_constants.py +91 -0
- azure/quantum/_workspace_connection_params.py +544 -0
- azure/quantum/version.py +1 -1
- azure/quantum/workspace.py +119 -147
- {azure_quantum-1.1.1.dist-info → azure_quantum-1.1.2.dev0.dist-info}/METADATA +1 -1
- {azure_quantum-1.1.1.dist-info → azure_quantum-1.1.2.dev0.dist-info}/RECORD +17 -15
- {azure_quantum-1.1.1.dist-info → azure_quantum-1.1.2.dev0.dist-info}/WHEEL +0 -0
- {azure_quantum-1.1.1.dist-info → azure_quantum-1.1.2.dev0.dist-info}/top_level.txt +0 -0
|
@@ -4,23 +4,14 @@
|
|
|
4
4
|
# ------------------------------------
|
|
5
5
|
import logging
|
|
6
6
|
|
|
7
|
+
import sys
|
|
7
8
|
from azure.core.exceptions import ClientAuthenticationError
|
|
8
|
-
|
|
9
9
|
from azure.identity import CredentialUnavailableError
|
|
10
|
+
from azure.core.credentials import AccessToken, TokenCredential
|
|
10
11
|
|
|
11
|
-
try:
|
|
12
|
-
from typing import TYPE_CHECKING
|
|
13
|
-
except ImportError:
|
|
14
|
-
TYPE_CHECKING = False
|
|
15
|
-
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
# pylint:disable=unused-import,ungrouped-imports
|
|
18
|
-
from typing import Any, Optional
|
|
19
|
-
from azure.core.credentials import AccessToken, TokenCredential
|
|
20
12
|
|
|
21
13
|
_LOGGER = logging.getLogger(__name__)
|
|
22
14
|
|
|
23
|
-
import sys
|
|
24
15
|
|
|
25
16
|
|
|
26
17
|
def filter_credential_warnings(record):
|
|
@@ -35,7 +26,7 @@ def _get_error_message(history):
|
|
|
35
26
|
attempts = []
|
|
36
27
|
for credential, error in history:
|
|
37
28
|
if error:
|
|
38
|
-
attempts.append("{
|
|
29
|
+
attempts.append(f"{credential.__class__.__name__}: {error}")
|
|
39
30
|
else:
|
|
40
31
|
attempts.append(credential.__class__.__name__)
|
|
41
32
|
return """
|
|
@@ -54,17 +45,21 @@ class _ChainedTokenCredential(object):
|
|
|
54
45
|
We also don't log a warning unless all credential attempts have failed.
|
|
55
46
|
"""
|
|
56
47
|
|
|
57
|
-
def __init__(self, *credentials):
|
|
58
|
-
|
|
59
|
-
self._successful_credential = None # type: Optional[TokenCredential]
|
|
48
|
+
def __init__(self, *credentials: TokenCredential):
|
|
49
|
+
self._successful_credential = None
|
|
60
50
|
self.credentials = credentials
|
|
61
51
|
|
|
62
|
-
def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
def get_token(self, *scopes: str, **kwargs) -> AccessToken: # pylint:disable=unused-argument
|
|
53
|
+
"""
|
|
54
|
+
Request a token from each chained credential, in order,
|
|
55
|
+
returning the first token received.
|
|
65
56
|
This method is called automatically by Azure SDK clients.
|
|
66
|
-
|
|
67
|
-
:
|
|
57
|
+
|
|
58
|
+
:param str scopes: desired scopes for the access token.
|
|
59
|
+
This method requires at least one scope.
|
|
60
|
+
|
|
61
|
+
:raises ~azure.core.exceptions.ClientAuthenticationError:
|
|
62
|
+
no credential in the chain provided a token
|
|
68
63
|
"""
|
|
69
64
|
history = []
|
|
70
65
|
|
|
@@ -85,7 +80,8 @@ class _ChainedTokenCredential(object):
|
|
|
85
80
|
self._successful_credential = credential
|
|
86
81
|
return token
|
|
87
82
|
except CredentialUnavailableError as ex:
|
|
88
|
-
# credential didn't attempt authentication because
|
|
83
|
+
# credential didn't attempt authentication because
|
|
84
|
+
# it lacks required data or state -> continue
|
|
89
85
|
history.append((credential, ex.message))
|
|
90
86
|
_LOGGER.info(
|
|
91
87
|
"%s - %s is unavailable",
|
|
@@ -93,7 +89,8 @@ class _ChainedTokenCredential(object):
|
|
|
93
89
|
credential.__class__.__name__,
|
|
94
90
|
)
|
|
95
91
|
except Exception as ex: # pylint: disable=broad-except
|
|
96
|
-
# credential failed to authenticate,
|
|
92
|
+
# credential failed to authenticate,
|
|
93
|
+
# or something unexpectedly raised -> break
|
|
97
94
|
history.append((credential, str(ex)))
|
|
98
95
|
# instead of logging a warning, we just want to log an info
|
|
99
96
|
# since other credentials might succeed
|
|
@@ -104,7 +101,8 @@ class _ChainedTokenCredential(object):
|
|
|
104
101
|
ex,
|
|
105
102
|
exc_info=_LOGGER.isEnabledFor(logging.DEBUG),
|
|
106
103
|
)
|
|
107
|
-
# here we do NOT want break and
|
|
104
|
+
# here we do NOT want break and
|
|
105
|
+
# will continue to try other credentials
|
|
108
106
|
|
|
109
107
|
finally:
|
|
110
108
|
# Re-enable warnings from credentials in Azure.Identity
|
|
@@ -3,36 +3,35 @@
|
|
|
3
3
|
# Licensed under the MIT License.
|
|
4
4
|
# ------------------------------------
|
|
5
5
|
import logging
|
|
6
|
-
import os
|
|
7
|
-
import requests
|
|
8
6
|
import re
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from azure.
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import urllib3
|
|
9
|
+
from azure.core.credentials import AccessToken
|
|
12
10
|
from azure.identity import (
|
|
13
11
|
AzurePowerShellCredential,
|
|
14
12
|
EnvironmentCredential,
|
|
15
13
|
ManagedIdentityCredential,
|
|
16
|
-
SharedTokenCacheCredential,
|
|
17
14
|
AzureCliCredential,
|
|
18
15
|
VisualStudioCodeCredential,
|
|
19
16
|
InteractiveBrowserCredential,
|
|
20
17
|
DeviceCodeCredential,
|
|
21
|
-
|
|
18
|
+
_internal as AzureIdentityInternals,
|
|
22
19
|
)
|
|
23
20
|
from ._chained import _ChainedTokenCredential
|
|
24
21
|
from ._token import _TokenFileCredential
|
|
25
|
-
|
|
26
|
-
try:
|
|
27
|
-
from typing import TYPE_CHECKING
|
|
28
|
-
except ImportError:
|
|
29
|
-
TYPE_CHECKING = False
|
|
30
|
-
|
|
31
|
-
if TYPE_CHECKING:
|
|
32
|
-
from typing import Any, List
|
|
33
|
-
from azure.core.credentials import AccessToken, TokenCredential
|
|
22
|
+
from azure.quantum._constants import ConnectionConstants
|
|
34
23
|
|
|
35
24
|
_LOGGER = logging.getLogger(__name__)
|
|
25
|
+
WWW_AUTHENTICATE_REGEX = re.compile(
|
|
26
|
+
r"""
|
|
27
|
+
^
|
|
28
|
+
Bearer\sauthorization_uri="
|
|
29
|
+
https://(?P<authority>[^/]*)/
|
|
30
|
+
(?P<tenant_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})
|
|
31
|
+
"
|
|
32
|
+
""",
|
|
33
|
+
re.VERBOSE | re.IGNORECASE)
|
|
34
|
+
WWW_AUTHENTICATE_HEADER_NAME = "WWW-Authenticate"
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
class _DefaultAzureCredential(_ChainedTokenCredential):
|
|
@@ -51,165 +50,104 @@ class _DefaultAzureCredential(_ChainedTokenCredential):
|
|
|
51
50
|
This is a mitigation for bug https://github.com/Azure/azure-sdk-for-python/issues/18975
|
|
52
51
|
We need the following parameters to enable auto-detection of tenant_id
|
|
53
52
|
- subscription_id
|
|
54
|
-
-
|
|
55
|
-
3) Add custom TokenFileCredential as first method to attempt,
|
|
53
|
+
- arm_endpoint (defaults to the production url "https://management.azure.com/")
|
|
54
|
+
3) Add custom TokenFileCredential as first method to attempt,
|
|
55
|
+
which will look for a local access token.
|
|
56
56
|
"""
|
|
57
|
-
def __init__(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"subscription_id", os.environ.get("SUBSCRIPTION_ID")
|
|
70
|
-
)
|
|
71
|
-
self.arm_base_url = kwargs.pop(
|
|
72
|
-
"arm_base_url", "https://management.azure.com/"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
self.managed_identity_client_id = kwargs.pop(
|
|
76
|
-
"managed_identity_client_id", os.environ.get(EnvironmentVariables.AZURE_CLIENT_ID)
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
self.shared_cache_username = kwargs.pop("shared_cache_username", os.environ.get(EnvironmentVariables.AZURE_USERNAME))
|
|
80
|
-
self.shared_cache_tenant_id = kwargs.pop(
|
|
81
|
-
"shared_cache_tenant_id", os.environ.get(EnvironmentVariables.AZURE_TENANT_ID)
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
self.vscode_tenant_id = kwargs.pop(
|
|
85
|
-
"visual_studio_code_tenant_id", os.environ.get(EnvironmentVariables.AZURE_TENANT_ID)
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
self.exclude_token_file_credential = kwargs.pop("exclude_token_file_credential", False)
|
|
89
|
-
self.exclude_environment_credential = kwargs.pop("exclude_environment_credential", False)
|
|
90
|
-
self.exclude_managed_identity_credential = kwargs.pop("exclude_managed_identity_credential", False)
|
|
91
|
-
self.exclude_shared_token_cache_credential = kwargs.pop("exclude_shared_token_cache_credential", True)
|
|
92
|
-
self.exclude_visual_studio_code_credential = kwargs.pop("exclude_visual_studio_code_credential", False)
|
|
93
|
-
self.exclude_cli_credential = kwargs.pop("exclude_cli_credential", False)
|
|
94
|
-
self.exclude_interactive_browser_credential = kwargs.pop("exclude_interactive_browser_credential", True)
|
|
95
|
-
self.exclude_device_code_credential = kwargs.pop("exclude_device_code_credential", False)
|
|
96
|
-
self.exclude_powershell_credential = kwargs.pop("exclude_powershell_credential", False)
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
arm_endpoint: str,
|
|
60
|
+
subscription_id: str,
|
|
61
|
+
client_id: Optional[str] = None,
|
|
62
|
+
tenant_id: Optional[str] = None,
|
|
63
|
+
authority: Optional[str] = None,
|
|
64
|
+
):
|
|
65
|
+
if arm_endpoint is None:
|
|
66
|
+
raise ValueError("arm_endpoint is mandatory parameter")
|
|
67
|
+
if subscription_id is None:
|
|
68
|
+
raise ValueError("subscription_id is mandatory parameter")
|
|
97
69
|
|
|
70
|
+
self.authority = self._authority_or_default(
|
|
71
|
+
authority=authority,
|
|
72
|
+
arm_endpoint=arm_endpoint)
|
|
73
|
+
self.tenant_id = tenant_id
|
|
74
|
+
self.subscription_id = subscription_id
|
|
75
|
+
self.arm_endpoint = arm_endpoint
|
|
76
|
+
self.client_id = client_id
|
|
98
77
|
# credentials will be created lazy on the first call to get_token
|
|
99
78
|
super(_DefaultAzureCredential, self).__init__()
|
|
100
79
|
|
|
101
|
-
def
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
self.shared_cache_tenant_id = self._get_tenant_id(arm_base_url=self.arm_base_url, subscription_id=self.subscription_id)
|
|
108
|
-
if self.interactive_browser_tenant_id is None:
|
|
109
|
-
self.interactive_browser_tenant_id = self._get_tenant_id(arm_base_url=self.arm_base_url, subscription_id=self.subscription_id)
|
|
110
|
-
|
|
111
|
-
credentials = [] # type: List[TokenCredential]
|
|
112
|
-
if not self.exclude_token_file_credential:
|
|
113
|
-
credentials.append(_TokenFileCredential())
|
|
114
|
-
if not self.exclude_environment_credential:
|
|
115
|
-
credentials.append(EnvironmentCredential(authority=self.authority))
|
|
116
|
-
if not self.exclude_managed_identity_credential:
|
|
117
|
-
credentials.append(ManagedIdentityCredential(client_id=self.managed_identity_client_id))
|
|
118
|
-
if not self.exclude_shared_token_cache_credential and SharedTokenCacheCredential.supported():
|
|
119
|
-
try:
|
|
120
|
-
# username and/or tenant_id are only required when the cache contains tokens for multiple identities
|
|
121
|
-
shared_cache = SharedTokenCacheCredential(
|
|
122
|
-
username=self.shared_cache_username, tenant_id=self.shared_cache_tenant_id, authority=self.authority
|
|
123
|
-
)
|
|
124
|
-
credentials.append(shared_cache)
|
|
125
|
-
except Exception as ex: # pylint:disable=broad-except
|
|
126
|
-
_LOGGER.info("Shared token cache is unavailable: '%s'", ex)
|
|
127
|
-
if not self.exclude_visual_studio_code_credential:
|
|
128
|
-
credentials.append(VisualStudioCodeCredential(tenant_id=self.vscode_tenant_id))
|
|
129
|
-
if not self.exclude_cli_credential:
|
|
130
|
-
credentials.append(AzureCliCredential())
|
|
131
|
-
if not self.exclude_powershell_credential:
|
|
132
|
-
credentials.append(AzurePowerShellCredential())
|
|
133
|
-
if not self.exclude_interactive_browser_credential:
|
|
134
|
-
credentials.append(InteractiveBrowserCredential(tenant_id=self.interactive_browser_tenant_id))
|
|
135
|
-
if not self.exclude_device_code_credential:
|
|
136
|
-
credentials.append(DeviceCodeCredential(tenant_id=self.interactive_browser_tenant_id))
|
|
80
|
+
def _authority_or_default(self, authority: str, arm_endpoint: str):
|
|
81
|
+
if authority:
|
|
82
|
+
return AzureIdentityInternals.normalize_authority(authority)
|
|
83
|
+
if arm_endpoint == ConnectionConstants.ARM_DOGFOOD_ENDPOINT:
|
|
84
|
+
return ConnectionConstants.DOGFOOD_AUTHORITY
|
|
85
|
+
return ConnectionConstants.AUTHORITY
|
|
137
86
|
|
|
87
|
+
def _initialize_credentials(self):
|
|
88
|
+
self._discover_tenant_id_(
|
|
89
|
+
arm_endpoint=self.arm_endpoint,
|
|
90
|
+
subscription_id=self.subscription_id)
|
|
91
|
+
credentials = []
|
|
92
|
+
credentials.append(_TokenFileCredential())
|
|
93
|
+
credentials.append(EnvironmentCredential())
|
|
94
|
+
if self.client_id:
|
|
95
|
+
credentials.append(ManagedIdentityCredential(client_id=self.client_id))
|
|
96
|
+
if self.authority and self.tenant_id:
|
|
97
|
+
credentials.append(VisualStudioCodeCredential(authority=self.authority, tenant_id=self.tenant_id))
|
|
98
|
+
credentials.append(AzureCliCredential(tenant_id=self.tenant_id))
|
|
99
|
+
credentials.append(AzurePowerShellCredential(tenant_id=self.tenant_id))
|
|
100
|
+
credentials.append(InteractiveBrowserCredential(authority=self.authority, tenant_id=self.tenant_id))
|
|
101
|
+
if self.client_id:
|
|
102
|
+
credentials.append(DeviceCodeCredential(authority=self.authority, client_id=self.client_id, tenant_id=self.tenant_id))
|
|
138
103
|
self.credentials = credentials
|
|
139
104
|
|
|
140
|
-
def get_token(self, *scopes, **kwargs):
|
|
141
|
-
|
|
142
|
-
|
|
105
|
+
def get_token(self, *scopes: str, **kwargs) -> AccessToken:
|
|
106
|
+
"""
|
|
107
|
+
Request an access token for `scopes`.
|
|
143
108
|
This method is called automatically by Azure SDK clients.
|
|
144
|
-
|
|
145
|
-
:
|
|
146
|
-
|
|
109
|
+
|
|
110
|
+
:param str scopes: desired scopes for the access token.
|
|
111
|
+
This method requires at least one scope.
|
|
112
|
+
|
|
113
|
+
:raises ~azure.core.exceptions.ClientAuthenticationError:authentication failed.
|
|
114
|
+
The exception has a `message` attribute listing each authentication
|
|
115
|
+
attempt and its error message.
|
|
147
116
|
"""
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
if self.credentials is None \
|
|
151
|
-
or len(self.credentials) == 0:
|
|
117
|
+
# lazy-initialize the credentials
|
|
118
|
+
if self.credentials is None or len(self.credentials) == 0:
|
|
152
119
|
self._initialize_credentials()
|
|
153
120
|
|
|
154
121
|
return super(_DefaultAzureCredential, self).get_token(*scopes, **kwargs)
|
|
155
122
|
|
|
156
|
-
def
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return self._successful_tenant_id
|
|
123
|
+
def _discover_tenant_id_(self, arm_endpoint:str, subscription_id:str):
|
|
124
|
+
"""
|
|
125
|
+
If the tenant_id was not given, try to obtain it
|
|
126
|
+
by calling the management endpoint for the subscription_id,
|
|
127
|
+
or by applying default values.
|
|
128
|
+
"""
|
|
129
|
+
if self.tenant_id:
|
|
130
|
+
return
|
|
165
131
|
|
|
166
132
|
try:
|
|
167
|
-
|
|
168
|
-
f"{
|
|
133
|
+
url = (
|
|
134
|
+
f"{arm_endpoint.rstrip('/')}/subscriptions/"
|
|
169
135
|
+ f"{subscription_id}?api-version=2018-01-01"
|
|
136
|
+
+ "&discover-tenant-id" # used by the test recording infrastructure
|
|
170
137
|
)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# We make a unauthenticated request to ARM and extract the tenant
|
|
176
|
-
# authority from the WWW-Authenticate header in the response.
|
|
177
|
-
# The header is of the form:
|
|
178
|
-
# Bearer authorization_uri=
|
|
179
|
-
# https://login.microsoftonline.com/tenantId, key1=value1s
|
|
180
|
-
auth_header = response.headers["WWW-Authenticate"]
|
|
181
|
-
_LOGGER.debug(
|
|
182
|
-
f'{"got the following auth header from"}'
|
|
183
|
-
f"the management endpoint: {auth_header}"
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
trimmed_auth_header = auth_header[
|
|
187
|
-
len("Bearer "):
|
|
188
|
-
] # trim the leading 'Bearer '
|
|
189
|
-
trimmed_auth_header_parts = trimmed_auth_header.split(
|
|
190
|
-
","
|
|
191
|
-
) # get the various k=v parts
|
|
192
|
-
key_value_pairs = dict(
|
|
193
|
-
map(lambda s: tuple(s.split("=")), trimmed_auth_header_parts)
|
|
194
|
-
) # make the parts into a dictionary
|
|
195
|
-
quoted_tenant_uri = key_value_pairs[
|
|
196
|
-
"authorization_uri"
|
|
197
|
-
] # get the value of the 'authorization_uri' key
|
|
198
|
-
tenant_uri = quoted_tenant_uri[
|
|
199
|
-
1:-1
|
|
200
|
-
] # strip it of surrounding quotes
|
|
201
|
-
|
|
202
|
-
_LOGGER.debug(
|
|
203
|
-
f'{"got the following tenant uri from"}'
|
|
204
|
-
+ f"the authentication header: {tenant_uri}"
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
regex = re.compile(pattern=r"([a-f0-9]+[-]){4}[a-f0-9]+", flags=re.IGNORECASE)
|
|
208
|
-
self._successful_tenant_id = regex.search(tenant_uri).group()
|
|
209
|
-
return self._successful_tenant_id
|
|
210
|
-
|
|
211
|
-
except Exception as e:
|
|
212
|
-
_LOGGER.debug(
|
|
213
|
-
f"Failed to get tenant authority for subscription: {e}"
|
|
138
|
+
http = urllib3.PoolManager()
|
|
139
|
+
response = http.request(
|
|
140
|
+
method="GET",
|
|
141
|
+
url=url,
|
|
214
142
|
)
|
|
215
|
-
|
|
143
|
+
if WWW_AUTHENTICATE_HEADER_NAME in response.headers:
|
|
144
|
+
www_authenticate = response.headers[WWW_AUTHENTICATE_HEADER_NAME]
|
|
145
|
+
match = re.search(WWW_AUTHENTICATE_REGEX, www_authenticate)
|
|
146
|
+
if match:
|
|
147
|
+
self.tenant_id = match.group("tenant_id")
|
|
148
|
+
# pylint: disable=broad-exception-caught
|
|
149
|
+
except Exception as ex:
|
|
150
|
+
_LOGGER.error(ex)
|
|
151
|
+
|
|
152
|
+
# apply default values
|
|
153
|
+
self.tenant_id = self.tenant_id or ConnectionConstants.MSA_TENANT_ID
|
|
@@ -10,71 +10,74 @@ import time
|
|
|
10
10
|
|
|
11
11
|
from azure.identity import CredentialUnavailableError
|
|
12
12
|
from azure.core.credentials import AccessToken
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
from typing import TYPE_CHECKING
|
|
16
|
-
except ImportError:
|
|
17
|
-
TYPE_CHECKING = False
|
|
18
|
-
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
# pylint:disable=unused-import,ungrouped-imports
|
|
21
|
-
from typing import Any
|
|
13
|
+
from azure.quantum._constants import EnvironmentVariables
|
|
22
14
|
|
|
23
15
|
_LOGGER = logging.getLogger(__name__)
|
|
24
16
|
|
|
25
|
-
_TOKEN_FILE_ENV_VARIABLE = "AZURE_QUANTUM_TOKEN_FILE"
|
|
26
|
-
|
|
27
17
|
|
|
28
18
|
class _TokenFileCredential(object):
|
|
29
19
|
"""
|
|
30
|
-
Implements a custom TokenCredential to use a local file as
|
|
20
|
+
Implements a custom TokenCredential to use a local file as
|
|
21
|
+
the source for an AzureQuantum token.
|
|
31
22
|
|
|
32
|
-
It will only use the local file if the AZURE_QUANTUM_TOKEN_FILE
|
|
33
|
-
|
|
23
|
+
It will only use the local file if the AZURE_QUANTUM_TOKEN_FILE
|
|
24
|
+
environment variable is set, and references an existing json file
|
|
25
|
+
that contains the access_token and expires_on timestamp in milliseconds.
|
|
34
26
|
|
|
35
|
-
If the environment variable is not set, the file does not exist,
|
|
36
|
-
|
|
27
|
+
If the environment variable is not set, the file does not exist,
|
|
28
|
+
or the token is invalid in any way (expired, for example),
|
|
29
|
+
then the credential will throw CredentialUnavailableError,
|
|
30
|
+
so that _ChainedTokenCredential can fallback to other methods.
|
|
37
31
|
"""
|
|
38
|
-
def __init__(self
|
|
39
|
-
|
|
40
|
-
self.token_file = os.environ.get(_TOKEN_FILE_ENV_VARIABLE)
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self.token_file = os.environ.get(EnvironmentVariables.QUANTUM_TOKEN_FILE)
|
|
41
34
|
if self.token_file:
|
|
42
|
-
_LOGGER.debug("Using provided token file location:
|
|
35
|
+
_LOGGER.debug("Using provided token file location: %s", self.token_file)
|
|
43
36
|
else:
|
|
44
|
-
_LOGGER.debug("No token file location provided for
|
|
37
|
+
_LOGGER.debug("No token file location provided for %s environment variable.",
|
|
38
|
+
EnvironmentVariables.QUANTUM_TOKEN_FILE)
|
|
45
39
|
|
|
46
|
-
def get_token(self, *scopes, **kwargs):
|
|
47
|
-
# type: (*str, **Any) -> AccessToken
|
|
40
|
+
def get_token(self, *scopes: str, **kwargs) -> AccessToken: # pylint:disable=unused-argument
|
|
48
41
|
"""Request an access token for `scopes`.
|
|
49
42
|
This method is called automatically by Azure SDK clients.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
This method only returns tokens for the https://quantum.microsoft.com/.default scope.
|
|
44
|
+
|
|
45
|
+
:param str scopes: desired scopes for the access token.
|
|
46
|
+
|
|
47
|
+
:raises ~azure.identity.CredentialUnavailableError
|
|
48
|
+
when failing to get the token.
|
|
49
|
+
The exception has a `message` attribute with the error message.
|
|
53
50
|
"""
|
|
54
51
|
if not self.token_file:
|
|
55
52
|
raise CredentialUnavailableError(message="Token file location not set.")
|
|
56
53
|
|
|
57
54
|
if not os.path.isfile(self.token_file):
|
|
58
|
-
raise CredentialUnavailableError(
|
|
55
|
+
raise CredentialUnavailableError(
|
|
56
|
+
message=f"Token file at {self.token_file} does not exist.")
|
|
59
57
|
|
|
60
58
|
try:
|
|
61
59
|
token = self._parse_token_file(self.token_file)
|
|
62
|
-
except JSONDecodeError:
|
|
63
|
-
raise CredentialUnavailableError(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
except JSONDecodeError as exception:
|
|
61
|
+
raise CredentialUnavailableError(
|
|
62
|
+
message="Failed to parse token file: Invalid JSON.") from exception
|
|
63
|
+
except KeyError as exception:
|
|
64
|
+
raise CredentialUnavailableError(
|
|
65
|
+
message="Failed to parse token file: Missing expected value: "
|
|
66
|
+
+ str(exception)) from exception
|
|
67
|
+
except Exception as exception:
|
|
68
|
+
raise CredentialUnavailableError(
|
|
69
|
+
message="Failed to parse token file: " + str(exception)) from exception
|
|
68
70
|
|
|
69
71
|
if token.expires_on <= time.time():
|
|
70
|
-
raise CredentialUnavailableError(
|
|
72
|
+
raise CredentialUnavailableError(
|
|
73
|
+
message=f"Token already expired at {time.asctime(time.gmtime(token.expires_on))}")
|
|
71
74
|
|
|
72
75
|
return token
|
|
73
76
|
|
|
74
|
-
def _parse_token_file(self, path):
|
|
75
|
-
|
|
76
|
-
with open(path, "r") as file:
|
|
77
|
+
def _parse_token_file(self, path) -> AccessToken:
|
|
78
|
+
with open(path, mode="r", encoding="utf-8") as file:
|
|
77
79
|
data = json.load(file)
|
|
78
|
-
|
|
80
|
+
# Convert ms to seconds, since python time.time only handles epoch time in seconds
|
|
81
|
+
expires_on = int(data["expires_on"]) / 1000
|
|
79
82
|
token = AccessToken(data["access_token"], expires_on)
|
|
80
83
|
return token
|
azure/quantum/_client/_client.py
CHANGED
|
@@ -10,6 +10,7 @@ from copy import deepcopy
|
|
|
10
10
|
from typing import Any, TYPE_CHECKING
|
|
11
11
|
|
|
12
12
|
from azure.core import PipelineClient
|
|
13
|
+
from azure.core.pipeline import policies
|
|
13
14
|
from azure.core.rest import HttpRequest, HttpResponse
|
|
14
15
|
|
|
15
16
|
from . import models as _models
|
|
@@ -44,6 +45,9 @@ class QuantumClient: # pylint: disable=client-accepts-api-version-keyword
|
|
|
44
45
|
:vartype sessions: azure.quantum._client.operations.SessionsOperations
|
|
45
46
|
:ivar top_level_items: TopLevelItemsOperations operations
|
|
46
47
|
:vartype top_level_items: azure.quantum._client.operations.TopLevelItemsOperations
|
|
48
|
+
:param azure_region: Supported Azure regions for Azure Quantum Services. For example, "eastus".
|
|
49
|
+
Required.
|
|
50
|
+
:type azure_region: str
|
|
47
51
|
:param subscription_id: The Azure subscription ID. This is a GUID-formatted string (e.g.
|
|
48
52
|
00000000-0000-0000-0000-000000000000). Required.
|
|
49
53
|
:type subscription_id: str
|
|
@@ -53,31 +57,47 @@ class QuantumClient: # pylint: disable=client-accepts-api-version-keyword
|
|
|
53
57
|
:type workspace_name: str
|
|
54
58
|
:param credential: Credential needed for the client to connect to Azure. Required.
|
|
55
59
|
:type credential: ~azure.core.credentials.TokenCredential
|
|
56
|
-
:keyword
|
|
57
|
-
:paramtype endpoint: str
|
|
58
|
-
:keyword api_version: Api Version. Default value is "2022-09-12-preview". Note that overriding
|
|
60
|
+
:keyword api_version: Api Version. Default value is "2023-11-13-preview". Note that overriding
|
|
59
61
|
this default value may result in unsupported behavior.
|
|
60
62
|
:paramtype api_version: str
|
|
61
63
|
"""
|
|
62
64
|
|
|
63
65
|
def __init__(
|
|
64
66
|
self,
|
|
67
|
+
azure_region: str,
|
|
65
68
|
subscription_id: str,
|
|
66
69
|
resource_group_name: str,
|
|
67
70
|
workspace_name: str,
|
|
68
71
|
credential: "TokenCredential",
|
|
69
|
-
*,
|
|
70
|
-
endpoint: str = "https://quantum.azure.com",
|
|
71
72
|
**kwargs: Any
|
|
72
73
|
) -> None:
|
|
74
|
+
_endpoint = kwargs.pop("endpoint", f"https://{azure_region}.quantum.azure.com")
|
|
73
75
|
self._config = QuantumClientConfiguration(
|
|
76
|
+
azure_region=azure_region,
|
|
74
77
|
subscription_id=subscription_id,
|
|
75
78
|
resource_group_name=resource_group_name,
|
|
76
79
|
workspace_name=workspace_name,
|
|
77
80
|
credential=credential,
|
|
78
81
|
**kwargs
|
|
79
82
|
)
|
|
80
|
-
|
|
83
|
+
_policies = kwargs.pop("policies", None)
|
|
84
|
+
if _policies is None:
|
|
85
|
+
_policies = [
|
|
86
|
+
policies.RequestIdPolicy(**kwargs),
|
|
87
|
+
self._config.headers_policy,
|
|
88
|
+
self._config.user_agent_policy,
|
|
89
|
+
self._config.proxy_policy,
|
|
90
|
+
policies.ContentDecodePolicy(**kwargs),
|
|
91
|
+
self._config.redirect_policy,
|
|
92
|
+
self._config.retry_policy,
|
|
93
|
+
self._config.authentication_policy,
|
|
94
|
+
self._config.custom_hook_policy,
|
|
95
|
+
self._config.logging_policy,
|
|
96
|
+
policies.DistributedTracingPolicy(**kwargs),
|
|
97
|
+
policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None,
|
|
98
|
+
self._config.http_logging_policy,
|
|
99
|
+
]
|
|
100
|
+
self._client: PipelineClient = PipelineClient(base_url=_endpoint, policies=_policies, **kwargs)
|
|
81
101
|
|
|
82
102
|
client_models = {k: v for k, v in _models._models.__dict__.items() if isinstance(v, type)}
|
|
83
103
|
client_models.update({k: v for k, v in _models.__dict__.items() if isinstance(v, type)})
|
|
@@ -91,7 +111,7 @@ class QuantumClient: # pylint: disable=client-accepts-api-version-keyword
|
|
|
91
111
|
self.sessions = SessionsOperations(self._client, self._config, self._serialize, self._deserialize)
|
|
92
112
|
self.top_level_items = TopLevelItemsOperations(self._client, self._config, self._serialize, self._deserialize)
|
|
93
113
|
|
|
94
|
-
def send_request(self, request: HttpRequest, **kwargs: Any) -> HttpResponse:
|
|
114
|
+
def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs: Any) -> HttpResponse:
|
|
95
115
|
"""Runs the network request through the client's chained policies.
|
|
96
116
|
|
|
97
117
|
>>> from azure.core.rest import HttpRequest
|
|
@@ -110,8 +130,14 @@ class QuantumClient: # pylint: disable=client-accepts-api-version-keyword
|
|
|
110
130
|
"""
|
|
111
131
|
|
|
112
132
|
request_copy = deepcopy(request)
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
path_format_arguments = {
|
|
134
|
+
"azureRegion": self._serialize.url(
|
|
135
|
+
"self._config.azure_region", self._config.azure_region, "str", skip_quote=True
|
|
136
|
+
),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments)
|
|
140
|
+
return self._client.send_request(request_copy, stream=stream, **kwargs) # type: ignore
|
|
115
141
|
|
|
116
142
|
def close(self) -> None:
|
|
117
143
|
self._client.close()
|