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.
@@ -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("{}: {}".format(credential.__class__.__name__, error))
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
- # type: (*TokenCredential) -> None
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
- # type: (*str, **Any) -> AccessToken
64
- """Request a token from each chained credential, in order, returning the first token received.
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
- :param str scopes: desired scopes for the access token. This method requires at least one scope.
67
- :raises ~azure.core.exceptions.ClientAuthenticationError: no credential in the chain provided a token
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 it lacks required data or state -> continue
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, or something unexpectedly raised -> break
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 will continue to try other credentials
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
- from azure.identity._constants import EnvironmentVariables
11
- from azure.identity._internal import get_default_authority, normalize_authority
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
- _credentials
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
- - arm_base_url (defaults to the production url "https://management.azure.com/")
55
- 3) Add custom TokenFileCredential as first method to attempt, which will look for a local access token.
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__(self, **kwargs):
58
- # type: (**Any) -> None
59
- self._successful_tenant_id = None
60
-
61
- self.authority = kwargs.pop("authority", None)
62
- self.authority = normalize_authority(self.authority) if self.authority else get_default_authority()
63
-
64
- self.interactive_browser_tenant_id = kwargs.pop(
65
- "interactive_browser_tenant_id", os.environ.get(EnvironmentVariables.AZURE_TENANT_ID)
66
- )
67
-
68
- self.subscription_id = kwargs.pop(
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 _initialize_credentials(self):
102
- if self.subscription_id is not None \
103
- and self.arm_base_url is not None:
104
- if self.vscode_tenant_id is None:
105
- self.vscode_tenant_id = self._get_tenant_id(arm_base_url=self.arm_base_url, subscription_id=self.subscription_id)
106
- if self.shared_cache_tenant_id is None:
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
- # type: (*str, **Any) -> AccessToken
142
- """Request an access token for `scopes`.
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
- :param str scopes: desired scopes for the access token. This method requires at least one scope.
145
- :raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The exception has a
146
- `message` attribute listing each authentication attempt and its error message.
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
- # add credentials the first time it runs the get_token
149
- # such that the _get_tenant_id can be called only when needed
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 _get_tenant_id(self, arm_base_url:str, subscription_id:str):
157
- if arm_base_url is None:
158
- raise ValueError("arm_base_url is mandatory parameter")
159
- if subscription_id is None:
160
- raise ValueError("subscription_id is mandatory parameter")
161
-
162
- # returns the cached tenant_id if available
163
- if self._successful_tenant_id is not None:
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
- uri = (
168
- f"{arm_base_url}/subscriptions/"
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
- response = requests.get(uri)
172
-
173
- # This gnarly piece of code is how we get the guest tenant
174
- # authority associated with the subscription.
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
- return None
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 the source for an AzureQuantum token.
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 environment variable is set, and references
33
- an existing json file that contains the access_token and expires_on timestamp in milliseconds.
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, or the token is invalid in any way (expired, for example),
36
- then the credential will throw CredentialUnavailableError, so that _ChainedTokenCredential can fallback to other methods.
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, **kwargs):
39
- # type: (**Any) -> None
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: {}".format(self.token_file))
35
+ _LOGGER.debug("Using provided token file location: %s", self.token_file)
43
36
  else:
44
- _LOGGER.debug("No token file location provided for {} environment variable.".format(_TOKEN_FILE_ENV_VARIABLE))
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): # pylint:disable=unused-argument
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
- :param str scopes: desired scopes for the access token. This method only returns tokens for the https://quantum.microsoft.com/.default scope.
51
- :raises ~azure.identity.CredentialUnavailableError: when failing to get token. The exception has a
52
- `message` attribute with the error message.
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(message="Token file at {} does not exist.".format(self.token_file))
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(message="Failed to parse token file: Invalid JSON.")
64
- except KeyError as e:
65
- raise CredentialUnavailableError(message="Failed to parse token file: Missing expected value: " + str(e))
66
- except Exception as e:
67
- raise CredentialUnavailableError(message="Failed to parse token file: " + str(e))
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(message="Token already expired at {}".format(time.asctime(time.gmtime(token.expires_on))))
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
- # type: (*str) -> AccessToken
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
- expires_on = int(data["expires_on"]) / 1000 # Convert ms to seconds, since python time.time only handles epoch time in seconds
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
@@ -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 endpoint: Service URL. Default value is "https://quantum.azure.com".
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
- self._client: PipelineClient = PipelineClient(base_url=endpoint, config=self._config, **kwargs)
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
- request_copy.url = self._client.format_url(request_copy.url)
114
- return self._client.send_request(request_copy, **kwargs)
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()