azure-quantum 3.5.0__tar.gz → 3.5.1.dev0__tar.gz

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.
Files changed (76) hide show
  1. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/PKG-INFO +4 -5
  2. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/README.md +3 -4
  3. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/_version.py +1 -1
  4. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_constants.py +69 -0
  5. azure_quantum-3.5.1.dev0/azure/quantum/_mgmt_client.py +239 -0
  6. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_workspace_connection_params.py +128 -27
  7. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/version.py +1 -1
  8. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/workspace.py +63 -5
  9. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure_quantum.egg-info/PKG-INFO +4 -5
  10. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure_quantum.egg-info/SOURCES.txt +1 -0
  11. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/__init__.py +0 -0
  12. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/__init__.py +0 -0
  13. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/_client.py +0 -0
  14. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/_configuration.py +0 -0
  15. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/_model_base.py +0 -0
  16. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/_patch.py +0 -0
  17. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/_serialization.py +0 -0
  18. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/models/__init__.py +0 -0
  19. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/models/_enums.py +0 -0
  20. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/models/_models.py +0 -0
  21. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/models/_patch.py +0 -0
  22. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/operations/__init__.py +0 -0
  23. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/operations/_operations.py +0 -0
  24. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/_client/operations/_patch.py +0 -0
  25. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/argument_types/__init__.py +0 -0
  26. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/argument_types/types.py +0 -0
  27. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/cirq/__init__.py +0 -0
  28. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/cirq/job.py +0 -0
  29. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/cirq/service.py +0 -0
  30. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/cirq/targets/__init__.py +0 -0
  31. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/cirq/targets/ionq.py +0 -0
  32. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/cirq/targets/quantinuum.py +0 -0
  33. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/cirq/targets/target.py +0 -0
  34. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/job/__init__.py +0 -0
  35. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/job/base_job.py +0 -0
  36. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/job/filtered_job.py +0 -0
  37. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/job/job.py +0 -0
  38. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/job/job_failed_with_results_error.py +0 -0
  39. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/job/session.py +0 -0
  40. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/job/workspace_item.py +0 -0
  41. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/job/workspace_item_factory.py +0 -0
  42. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/__init__.py +0 -0
  43. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/backends/__init__.py +0 -0
  44. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/backends/_qiskit_ionq.py +0 -0
  45. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/backends/backend.py +0 -0
  46. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/backends/ionq.py +0 -0
  47. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/backends/qci.py +0 -0
  48. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/backends/quantinuum.py +0 -0
  49. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/backends/rigetti.py +0 -0
  50. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/job.py +0 -0
  51. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/qiskit/provider.py +0 -0
  52. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/storage.py +0 -0
  53. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/__init__.py +0 -0
  54. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/ionq.py +0 -0
  55. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/params.py +0 -0
  56. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/pasqal/__init__.py +0 -0
  57. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/pasqal/result.py +0 -0
  58. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/pasqal/target.py +0 -0
  59. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/quantinuum.py +0 -0
  60. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/rigetti/__init__.py +0 -0
  61. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/rigetti/result.py +0 -0
  62. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/rigetti/target.py +0 -0
  63. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/target.py +0 -0
  64. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure/quantum/target/target_factory.py +0 -0
  65. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure_quantum.egg-info/dependency_links.txt +0 -0
  66. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure_quantum.egg-info/requires.txt +0 -0
  67. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/azure_quantum.egg-info/top_level.txt +0 -0
  68. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/requirements-cirq.txt +0 -0
  69. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/requirements-dev.txt +0 -0
  70. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/requirements-pulser.txt +0 -0
  71. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/requirements-qiskit.txt +0 -0
  72. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/requirements-qsharp.txt +0 -0
  73. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/requirements-quil.txt +0 -0
  74. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/requirements.txt +0 -0
  75. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/setup.cfg +0 -0
  76. {azure_quantum-3.5.0 → azure_quantum-3.5.1.dev0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azure-quantum
3
- Version: 3.5.0
3
+ Version: 3.5.1.dev0
4
4
  Summary: Python client for Azure Quantum
5
5
  Home-page: https://github.com/microsoft/azure-quantum-python
6
6
  Author: Microsoft
@@ -79,15 +79,14 @@ To get started, visit the following Quickstart guides:
79
79
 
80
80
  ## General usage ##
81
81
 
82
- To connect to your Azure Quantum Workspace, go to the [Azure Portal](https://portal.azure.com), navigate to your Workspace and copy-paste the resource ID and location into the code snippet below.
82
+ To connect to your Azure Quantum Workspace, go to the [Azure Portal](https://portal.azure.com), navigate to your Workspace and copy-paste the resource ID into the code snippet below.
83
83
 
84
84
  ```python
85
85
  from azure.quantum import Workspace
86
86
 
87
- # Enter your Workspace details (resource ID and location) below
87
+ # Enter your Workspace resource ID below
88
88
  workspace = Workspace(
89
- resource_id="",
90
- location=""
89
+ resource_id=""
91
90
  )
92
91
  ```
93
92
 
@@ -29,15 +29,14 @@ To get started, visit the following Quickstart guides:
29
29
 
30
30
  ## General usage ##
31
31
 
32
- To connect to your Azure Quantum Workspace, go to the [Azure Portal](https://portal.azure.com), navigate to your Workspace and copy-paste the resource ID and location into the code snippet below.
32
+ To connect to your Azure Quantum Workspace, go to the [Azure Portal](https://portal.azure.com), navigate to your Workspace and copy-paste the resource ID into the code snippet below.
33
33
 
34
34
  ```python
35
35
  from azure.quantum import Workspace
36
36
 
37
- # Enter your Workspace details (resource ID and location) below
37
+ # Enter your Workspace resource ID below
38
38
  workspace = Workspace(
39
- resource_id="",
40
- location=""
39
+ resource_id=""
41
40
  )
42
41
  ```
43
42
 
@@ -6,4 +6,4 @@
6
6
  # Changes may cause incorrect behavior and will be lost if the code is regenerated.
7
7
  # --------------------------------------------------------------------------
8
8
 
9
- VERSION = "3.5.0"
9
+ VERSION = "3.5.1.dev0"
@@ -55,6 +55,9 @@ class ConnectionConstants:
55
55
  DATA_PLANE_CREDENTIAL_SCOPE = "https://quantum.microsoft.com/.default"
56
56
  ARM_CREDENTIAL_SCOPE = "https://management.azure.com/.default"
57
57
 
58
+ DEFAULT_ARG_API_VERSION = "2021-03-01"
59
+ DEFAULT_WORKSPACE_API_VERSION = "2025-11-01-preview"
60
+
58
61
  MSA_TENANT_ID = "9188040d-6c67-4c5b-b112-36a304b66dad"
59
62
 
60
63
  AUTHORITY = AzureIdentityInternals.get_default_authority()
@@ -63,10 +66,14 @@ class ConnectionConstants:
63
66
  # pylint: disable=unnecessary-lambda-assignment
64
67
  GET_QUANTUM_PRODUCTION_ENDPOINT = \
65
68
  lambda location: f"https://{location}.quantum.azure.com/"
69
+ GET_QUANTUM_PRODUCTION_ENDPOINT_v2 = \
70
+ lambda location: f"https://{location}-v2.quantum.azure.com/"
66
71
  GET_QUANTUM_CANARY_ENDPOINT = \
67
72
  lambda location: f"https://{location or 'eastus2euap'}.quantum.azure.com/"
68
73
  GET_QUANTUM_DOGFOOD_ENDPOINT = \
69
74
  lambda location: f"https://{location}.quantum-test.azure.com/"
75
+ GET_QUANTUM_DOGFOOD_ENDPOINT_v2 = \
76
+ lambda location: f"https://{location}-v2.quantum-test.azure.com/"
70
77
 
71
78
  ARM_PRODUCTION_ENDPOINT = "https://management.azure.com/"
72
79
  ARM_DOGFOOD_ENDPOINT = "https://api-dogfood.resources.windows-int.net/"
@@ -93,3 +100,65 @@ class ConnectionConstants:
93
100
  GUID_REGEX_PATTERN = (
94
101
  r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
95
102
  )
103
+
104
+ VALID_WORKSPACE_NAME_PATTERN = r"^[a-zA-Z0-9]+(-*[a-zA-Z0-9])*$"
105
+
106
+ VALID_AZURE_REGIONS = {
107
+ "australiacentral",
108
+ "australiacentral2",
109
+ "australiaeast",
110
+ "australiasoutheast",
111
+ "austriaeast",
112
+ "belgiumcentral",
113
+ "brazilsouth",
114
+ "brazilsoutheast",
115
+ "canadacentral",
116
+ "canadaeast",
117
+ "centralindia",
118
+ "centralus",
119
+ "centraluseuap",
120
+ "chilecentral",
121
+ "eastasia",
122
+ "eastus",
123
+ "eastus2",
124
+ "eastus2euap",
125
+ "francecentral",
126
+ "francesouth",
127
+ "germanynorth",
128
+ "germanywestcentral",
129
+ "indonesiacentral",
130
+ "israelcentral",
131
+ "italynorth",
132
+ "japaneast",
133
+ "japanwest",
134
+ "koreacentral",
135
+ "koreasouth",
136
+ "malaysiawest",
137
+ "mexicocentral",
138
+ "newzealandnorth",
139
+ "northcentralus",
140
+ "northeurope",
141
+ "norwayeast",
142
+ "norwaywest",
143
+ "polandcentral",
144
+ "qatarcentral",
145
+ "southafricanorth",
146
+ "southafricawest",
147
+ "southcentralus",
148
+ "southindia",
149
+ "southeastasia",
150
+ "spaincentral",
151
+ "swedencentral",
152
+ "switzerlandnorth",
153
+ "switzerlandwest",
154
+ "uaecentral",
155
+ "uaenorth",
156
+ "uksouth",
157
+ "ukwest",
158
+ "westcentralus",
159
+ "westeurope",
160
+ "westindia",
161
+ "westus",
162
+ "westus2",
163
+ "westus3",
164
+ }
@@ -0,0 +1,239 @@
1
+ ##
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License.
4
+ ##
5
+ """
6
+ Module providing the WorkspaceMgmtClient class for managing workspace operations.
7
+ Created to do not add additional azure-mgmt-* dependencies that can conflict with existing ones.
8
+ """
9
+
10
+ import logging
11
+ from http import HTTPStatus
12
+ from typing import Any, Optional, cast
13
+ from azure.core import PipelineClient
14
+ from azure.core.credentials import TokenProvider
15
+ from azure.core.pipeline import policies
16
+ from azure.core.rest import HttpRequest
17
+ from azure.core.exceptions import HttpResponseError
18
+ from azure.quantum._workspace_connection_params import WorkspaceConnectionParams
19
+ from azure.quantum._constants import ConnectionConstants
20
+ from azure.quantum._client._configuration import VERSION
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ __all__ = ["WorkspaceMgmtClient"]
25
+
26
+
27
+ class WorkspaceMgmtClient():
28
+ """
29
+ Client for Azure Quantum Workspace related ARM/ARG operations.
30
+ Uses PipelineClient under the hood which is standard for all Azure SDK clients,
31
+ see https://learn.microsoft.com/en-us/azure/developer/python/sdk/fundamentals/http-pipeline-retries.
32
+
33
+ :param credential:
34
+ The credential to use to connect to Azure services.
35
+
36
+ :param base_url:
37
+ The base URL for the ARM endpoint.
38
+
39
+ :param user_agent:
40
+ Add the specified value as a prefix to the HTTP User-Agent header.
41
+ """
42
+
43
+ # Constants
44
+ DEFAULT_RETRY_TOTAL = 3
45
+ CONTENT_TYPE_JSON = "application/json"
46
+ CONNECT_DOC_LINK = "https://learn.microsoft.com/en-us/azure/quantum/how-to-connect-workspace"
47
+ CONNECT_DOC_MESSAGE = f"To find details on how to connect to your workspace, please see {CONNECT_DOC_LINK}."
48
+
49
+ def __init__(self, credential: TokenProvider, base_url: str, user_agent: Optional[str] = None) -> None:
50
+ """
51
+ Initialize the WorkspaceMgmtClient.
52
+
53
+ :param credential:
54
+ The credential to use to connect to Azure services.
55
+
56
+ :param base_url:
57
+ The base URL for the ARM endpoint.
58
+ """
59
+ self._credential = credential
60
+ self._base_url = base_url
61
+ self._policies = [
62
+ policies.RequestIdPolicy(),
63
+ policies.HeadersPolicy({
64
+ "Content-Type": self.CONTENT_TYPE_JSON,
65
+ "Accept": self.CONTENT_TYPE_JSON,
66
+ }),
67
+ policies.UserAgentPolicy(user_agent=user_agent, sdk_moniker="quantum/{}".format(VERSION)),
68
+ policies.RetryPolicy(retry_total=self.DEFAULT_RETRY_TOTAL),
69
+ policies.BearerTokenCredentialPolicy(self._credential, ConnectionConstants.ARM_CREDENTIAL_SCOPE),
70
+ ]
71
+ self._client: PipelineClient = PipelineClient(base_url=cast(str, base_url), policies=self._policies)
72
+
73
+ def close(self) -> None:
74
+ self._client.close()
75
+
76
+ def __enter__(self) -> 'WorkspaceMgmtClient':
77
+ self._client.__enter__()
78
+ return self
79
+
80
+ def __exit__(self, *exc_details: Any) -> None:
81
+ self._client.__exit__(*exc_details)
82
+
83
+ def load_workspace_from_arg(self, connection_params: WorkspaceConnectionParams) -> None:
84
+ """
85
+ Queries Azure Resource Graph to find a workspace by name and optionally location, resource group, subscription.
86
+ Provided workspace name, location, resource group, and subscription in connection params must be validated beforehand.
87
+
88
+ :param connection_params:
89
+ The workspace connection parameters to use and update.
90
+ """
91
+ if not connection_params.workspace_name:
92
+ raise ValueError("Workspace name must be specified to try to load workspace details from ARG.")
93
+
94
+ query = f"""
95
+ Resources
96
+ | where type =~ 'microsoft.quantum/workspaces'
97
+ | where name =~ '{connection_params.workspace_name}'
98
+ """
99
+
100
+ if connection_params.resource_group:
101
+ query += f"\n | where resourceGroup =~ '{connection_params.resource_group}'"
102
+
103
+ if connection_params.location:
104
+ query += f"\n | where location =~ '{connection_params.location}'"
105
+
106
+ query += """
107
+ | extend endpointUri = tostring(properties.endpointUri)
108
+ | project name, subscriptionId, resourceGroup, location, endpointUri
109
+ """
110
+
111
+ request_body = {
112
+ "query": query
113
+ }
114
+
115
+ if connection_params.subscription_id:
116
+ request_body["subscriptions"] = [connection_params.subscription_id]
117
+
118
+ # Create request to Azure Resource Graph API
119
+ request = HttpRequest(
120
+ method="POST",
121
+ url=self._client.format_url("/providers/Microsoft.ResourceGraph/resources"),
122
+ params={"api-version": ConnectionConstants.DEFAULT_ARG_API_VERSION},
123
+ json=request_body
124
+ )
125
+
126
+ try:
127
+ response = self._client.send_request(request)
128
+ response.raise_for_status()
129
+ result = response.json()
130
+ except Exception as e:
131
+ raise RuntimeError(
132
+ f"Could not load workspace details from Azure Resource Graph: {str(e)}.\n{self.CONNECT_DOC_MESSAGE}"
133
+ ) from e
134
+
135
+ data = result.get('data', [])
136
+
137
+ if not data:
138
+ raise ValueError(f"No matching workspace found with name '{connection_params.workspace_name}'. {self.CONNECT_DOC_MESSAGE}")
139
+
140
+ if len(data) > 1:
141
+ raise ValueError(
142
+ f"Multiple Azure Quantum workspaces found with name '{connection_params.workspace_name}'. "
143
+ f"Please specify additional connection parameters. {self.CONNECT_DOC_MESSAGE}"
144
+ )
145
+
146
+ workspace_data = data[0]
147
+
148
+ connection_params.subscription_id = workspace_data.get('subscriptionId')
149
+ connection_params.resource_group = workspace_data.get('resourceGroup')
150
+ connection_params.location = workspace_data.get('location')
151
+ connection_params.quantum_endpoint = workspace_data.get('endpointUri')
152
+
153
+ logger.debug(
154
+ "Found workspace '%s' in subscription '%s', resource group '%s', location '%s', endpoint '%s'",
155
+ connection_params.workspace_name,
156
+ connection_params.subscription_id,
157
+ connection_params.resource_group,
158
+ connection_params.location,
159
+ connection_params.quantum_endpoint
160
+ )
161
+
162
+ # If one of the required parameters is missing, probably workspace in failed provisioning state
163
+ if not connection_params.is_complete():
164
+ raise ValueError(
165
+ f"Failed to retrieve complete workspace details for workspace '{connection_params.workspace_name}'. "
166
+ "Please check that workspace is in valid state."
167
+ )
168
+
169
+ def load_workspace_from_arm(self, connection_params: WorkspaceConnectionParams) -> None:
170
+ """
171
+ Fetches the workspace resource from ARM and sets location and endpoint URI params.
172
+ Provided workspace name, resource group, and subscription in connection params must be validated beforehand.
173
+
174
+ :param connection_params:
175
+ The workspace connection parameters to use and update.
176
+ """
177
+ if not all([connection_params.subscription_id, connection_params.resource_group, connection_params.workspace_name]):
178
+ raise ValueError("Missing required connection parameters to load workspace details from ARM.")
179
+
180
+ api_version = connection_params.api_version or ConnectionConstants.DEFAULT_WORKSPACE_API_VERSION
181
+
182
+ url = (
183
+ f"/subscriptions/{connection_params.subscription_id}"
184
+ f"/resourceGroups/{connection_params.resource_group}"
185
+ f"/providers/Microsoft.Quantum/workspaces/{connection_params.workspace_name}"
186
+ )
187
+
188
+ request = HttpRequest(
189
+ method="GET",
190
+ url=self._client.format_url(url),
191
+ params={"api-version": api_version},
192
+ )
193
+
194
+ try:
195
+ response = self._client.send_request(request)
196
+ response.raise_for_status()
197
+ workspace_data = response.json()
198
+ except HttpResponseError as e:
199
+ if e.status_code == HTTPStatus.NOT_FOUND:
200
+ raise ValueError(
201
+ f"Azure Quantum workspace '{connection_params.workspace_name}' "
202
+ f"not found in resource group '{connection_params.resource_group}' "
203
+ f"and subscription '{connection_params.subscription_id}'. "
204
+ f"{self.CONNECT_DOC_MESSAGE}"
205
+ ) from e
206
+ # Re-raise for other HTTP errors
207
+ raise
208
+ except Exception as e:
209
+ raise RuntimeError(
210
+ f"Could not load workspace details from ARM: {str(e)}.\n{self.CONNECT_DOC_MESSAGE}"
211
+ ) from e
212
+
213
+ # Extract and apply location
214
+ location = workspace_data.get("location")
215
+ if location:
216
+ connection_params.location = location
217
+ logger.debug(
218
+ "Updated workspace location from ARM: %s",
219
+ location
220
+ )
221
+ else:
222
+ raise ValueError(
223
+ f"Failed to retrieve location for workspace '{connection_params.workspace_name}'. "
224
+ f"Please check that workspace is in valid state."
225
+ )
226
+
227
+ # Extract and apply endpoint URI from properties
228
+ properties = workspace_data.get("properties", {})
229
+ endpoint_uri = properties.get("endpointUri")
230
+ if endpoint_uri:
231
+ connection_params.quantum_endpoint = endpoint_uri
232
+ logger.debug(
233
+ "Updated workspace endpoint from ARM: %s", connection_params.quantum_endpoint
234
+ )
235
+ else:
236
+ raise ValueError(
237
+ f"Failed to retrieve endpoint uri for workspace '{connection_params.workspace_name}'. "
238
+ f"Please check that workspace is in valid state."
239
+ )
@@ -20,6 +20,8 @@ from azure.quantum._constants import (
20
20
  EnvironmentVariables,
21
21
  ConnectionConstants,
22
22
  GUID_REGEX_PATTERN,
23
+ VALID_WORKSPACE_NAME_PATTERN,
24
+ VALID_AZURE_REGIONS,
23
25
  )
24
26
 
25
27
  class WorkspaceConnectionParams:
@@ -46,9 +48,19 @@ class WorkspaceConnectionParams:
46
48
  ResourceGroupName=(?P<resource_group>[^\s;]+);
47
49
  WorkspaceName=(?P<workspace_name>[^\s;]+);
48
50
  ApiKey=(?P<api_key>[^\s;]+);
49
- QuantumEndpoint=(?P<quantum_endpoint>https://(?P<location>[^\s\.]+).quantum(?:-test)?.azure.com/);
51
+ QuantumEndpoint=(?P<quantum_endpoint>https://(?P<location>[a-zA-Z0-9]+)(?:-v2)?.quantum(?:-test)?.azure.com/);
50
52
  """,
51
53
  re.VERBOSE | re.IGNORECASE)
54
+
55
+ WORKSPACE_NOT_FULLY_SPECIFIED_MSG = """
56
+ Azure Quantum workspace not fully specified.
57
+ Please specify one of the following:
58
+ 1) A valid resource ID.
59
+ 2) A valid combination of subscription ID,
60
+ resource group name, and workspace name.
61
+ 3) A valid connection string (via Workspace.from_connection_string()).
62
+ 4) A valid workspace name.
63
+ """
52
64
 
53
65
  def __init__(
54
66
  self,
@@ -85,6 +97,8 @@ class WorkspaceConnectionParams:
85
97
  self.client_id = None
86
98
  self.tenant_id = None
87
99
  self.api_version = None
100
+ # Track if connection string was used
101
+ self._used_connection_string = False
88
102
  # callback to create a new client if needed
89
103
  # for example, when changing the user agent
90
104
  self.on_new_client_request = on_new_client_request
@@ -108,6 +122,81 @@ class WorkspaceConnectionParams:
108
122
  workspace_name=workspace_name,
109
123
  )
110
124
  self.apply_resource_id(resource_id=resource_id)
125
+ # Validate connection parameters if they are set
126
+ self._validate_connection_params()
127
+
128
+ def _validate_connection_params(self):
129
+ self._validate_subscription_id()
130
+ self._validate_resource_group()
131
+ self._validate_workspace_name()
132
+ self._validate_location()
133
+
134
+ def _validate_subscription_id(self):
135
+ # Validate that subscription id is a valid GUID
136
+ if self.subscription_id is not None:
137
+ if not isinstance(self.subscription_id, str):
138
+ raise ValueError("Subscription ID must be a string.")
139
+ if not re.match(f"^{GUID_REGEX_PATTERN}$", self.subscription_id, re.IGNORECASE):
140
+ raise ValueError("Subscription ID must be a valid GUID.")
141
+
142
+ def _validate_resource_group(self):
143
+ # Validate resource group, see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftresources
144
+ # Length 1-90, valid characters: alphanumeric, underscore, parentheses, hyphen, period (except at end), and Unicode characters:
145
+ # Uppercase Letter - Signified by the Unicode designation "Lu" (letter, uppercase);
146
+ # Lowercase Letter - Signified by the Unicode designation "Ll" (letter, lowercase);
147
+ # Titlecase Letter - Signified by the Unicode designation "Lt" (letter, titlecase);
148
+ # Modifier Letter - Signified by the Unicode designation "Lm" (letter, modifier);
149
+ # Other Letter - Signified by the Unicode designation "Lo" (letter, other);
150
+ # Decimal Digit Number - Signified by the Unicode designation "Nd" (number, decimal digit).
151
+ if self.resource_group is not None:
152
+ if not isinstance(self.resource_group, str):
153
+ raise ValueError("Resource group name must be a string.")
154
+
155
+ if len(self.resource_group) < 1 or len(self.resource_group) > 90:
156
+ raise ValueError(
157
+ "Resource group name must be between 1 and 90 characters long."
158
+ )
159
+
160
+ err_msg = "Resource group name can only include alphanumeric, underscore, parentheses, hyphen, period (except at end), and Unicode characters that match the allowed characters."
161
+ if self.resource_group.endswith('.'):
162
+ raise ValueError(err_msg)
163
+
164
+ import unicodedata
165
+ for i, char in enumerate(self.resource_group):
166
+ category = unicodedata.category(char)
167
+ if not (
168
+ char in ('_', '(', ')', '-', '.') or
169
+ category in ('Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nd')
170
+ ):
171
+ raise ValueError(err_msg)
172
+
173
+ def _validate_workspace_name(self):
174
+ # Validate workspace name, see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftquantum
175
+ # Length 2-54, valid characters: alphanumerics (a-zA-Z0-9) and hyphens, can't start or end with hyphen
176
+ if self.workspace_name is not None:
177
+ if not isinstance(self.workspace_name, str):
178
+ raise ValueError("Workspace name must be a string.")
179
+
180
+ if len(self.workspace_name) < 2 or len(self.workspace_name) > 54:
181
+ raise ValueError(
182
+ "Workspace name must be between 2 and 54 characters long."
183
+ )
184
+
185
+ err_msg = "Workspace name can only include alphanumerics (a-zA-Z0-9) and hyphens, and cannot start or end with hyphen."
186
+
187
+ if self.workspace_name.startswith('-') or self.workspace_name.endswith('-'):
188
+ raise ValueError(err_msg)
189
+
190
+ if not re.match(VALID_WORKSPACE_NAME_PATTERN, self.workspace_name):
191
+ raise ValueError(err_msg)
192
+
193
+ def _validate_location(self):
194
+ # Validate that location is one of the Azure regions https://learn.microsoft.com/en-us/azure/reliability/regions-list
195
+ if self.location is not None:
196
+ if not isinstance(self.location, str):
197
+ raise ValueError("Location must be a string.")
198
+ if self.location not in VALID_AZURE_REGIONS:
199
+ raise ValueError(f"Location must be one of the Azure regions listed in https://learn.microsoft.com/en-us/azure/reliability/regions-list.")
111
200
 
112
201
  @property
113
202
  def location(self):
@@ -142,19 +231,8 @@ class WorkspaceConnectionParams:
142
231
  def quantum_endpoint(self):
143
232
  """
144
233
  The Azure Quantum data plane endpoint.
145
- Defaults to well-known endpoint based on the environment.
146
- """
147
- if self._quantum_endpoint:
148
- return self._quantum_endpoint
149
- if not self.location:
150
- raise ValueError("Location not specified")
151
- if self.environment is EnvironmentKind.PRODUCTION:
152
- return ConnectionConstants.GET_QUANTUM_PRODUCTION_ENDPOINT(self.location)
153
- if self.environment is EnvironmentKind.CANARY:
154
- return ConnectionConstants.GET_QUANTUM_CANARY_ENDPOINT(self.location)
155
- if self.environment is EnvironmentKind.DOGFOOD:
156
- return ConnectionConstants.GET_QUANTUM_DOGFOOD_ENDPOINT(self.location)
157
- raise ValueError(f"Unknown environment `{self.environment}`.")
234
+ """
235
+ return self._quantum_endpoint
158
236
 
159
237
  @quantum_endpoint.setter
160
238
  def quantum_endpoint(self, value: str):
@@ -235,6 +313,7 @@ class WorkspaceConnectionParams:
235
313
  if not match:
236
314
  raise ValueError("Invalid connection string")
237
315
  self._merge_re_match(match)
316
+ self._used_connection_string = True
238
317
 
239
318
  def merge(
240
319
  self,
@@ -450,6 +529,32 @@ class WorkspaceConnectionParams:
450
529
  full_user_agent = (f"{app_id} {full_user_agent}"
451
530
  if full_user_agent else app_id)
452
531
  return full_user_agent
532
+
533
+ def have_enough_for_discovery(self) -> bool:
534
+ """
535
+ Returns true if we have enough parameters
536
+ to try to find the Azure Quantum Workspace.
537
+ """
538
+ return (self.workspace_name
539
+ and self.get_credential_or_default())
540
+
541
+ def assert_have_enough_for_discovery(self):
542
+ """
543
+ Raises ValueError if we don't have enough parameters
544
+ to try to find the Azure Quantum Workspace.
545
+ """
546
+ if not self.have_enough_for_discovery():
547
+ raise ValueError(self.WORKSPACE_NOT_FULLY_SPECIFIED_MSG)
548
+
549
+ def can_build_resource_id(self) -> bool:
550
+ """
551
+ Returns true if we have all necessary parameters
552
+ to identify the Azure Quantum Workspace resource.
553
+ """
554
+ return (self.subscription_id
555
+ and self.resource_group
556
+ and self.workspace_name
557
+ and self.get_credential_or_default())
453
558
 
454
559
  def is_complete(self) -> bool:
455
560
  """
@@ -460,6 +565,7 @@ class WorkspaceConnectionParams:
460
565
  and self.subscription_id
461
566
  and self.resource_group
462
567
  and self.workspace_name
568
+ and self.quantum_endpoint
463
569
  and self.get_credential_or_default())
464
570
 
465
571
  def assert_complete(self):
@@ -468,15 +574,7 @@ class WorkspaceConnectionParams:
468
574
  to connect to the Azure Quantum Workspace.
469
575
  """
470
576
  if not self.is_complete():
471
- raise ValueError(
472
- """
473
- Azure Quantum workspace not fully specified.
474
- Please specify one of the following:
475
- 1) A valid combination of location and resource ID.
476
- 2) A valid combination of location, subscription ID,
477
- resource group name, and workspace name.
478
- 3) A valid connection string (via Workspace.from_connection_string()).
479
- """)
577
+ raise ValueError(self.WORKSPACE_NOT_FULLY_SPECIFIED_MSG)
480
578
 
481
579
  def default_from_env_vars(self) -> WorkspaceConnectionParams:
482
580
  """
@@ -512,10 +610,13 @@ class WorkspaceConnectionParams:
512
610
  or not self.workspace_name
513
611
  or not self.credential
514
612
  ):
515
- self._merge_connection_params(
516
- connection_params=WorkspaceConnectionParams(
517
- connection_string=os.environ.get(EnvironmentVariables.CONNECTION_STRING)),
518
- merge_default_mode=True)
613
+ env_connection_string = os.environ.get(EnvironmentVariables.CONNECTION_STRING)
614
+ if env_connection_string:
615
+ self._merge_connection_params(
616
+ connection_params=WorkspaceConnectionParams(
617
+ connection_string=env_connection_string),
618
+ merge_default_mode=True)
619
+ self._used_connection_string = True
519
620
  return self
520
621
 
521
622
  @classmethod
@@ -5,4 +5,4 @@
5
5
  # Copyright (c) Microsoft Corporation. All rights reserved.
6
6
  # Licensed under the MIT License.
7
7
  ##
8
- __version__ = "3.5.0"
8
+ __version__ = "3.5.1.dev0"
@@ -21,6 +21,7 @@ from typing import (
21
21
  Tuple,
22
22
  Union,
23
23
  )
24
+ from typing_extensions import Self
24
25
  from azure.core.paging import ItemPaged
25
26
  from azure.quantum._client import ServicesClient
26
27
  from azure.quantum._client.models import JobDetails, ItemDetails, SessionDetails
@@ -49,6 +50,7 @@ from azure.quantum.storage import (
49
50
  get_container_uri,
50
51
  ContainerClient
51
52
  )
53
+ from azure.quantum._mgmt_client import WorkspaceMgmtClient
52
54
  if TYPE_CHECKING:
53
55
  from azure.quantum.target import Target
54
56
 
@@ -62,10 +64,11 @@ class Workspace:
62
64
  """
63
65
  Represents an Azure Quantum workspace.
64
66
 
65
- When creating a Workspace object, callers have two options for
67
+ When creating a Workspace object, callers have several options for
66
68
  identifying the Azure Quantum workspace (in order of precedence):
67
- 1. specify a valid location and resource ID; or
68
- 2. specify a valid location, subscription ID, resource group, and workspace name.
69
+ 1. specify a valid resource ID; or
70
+ 2. specify a valid subscription ID, resource group, and workspace name; or
71
+ 3. specify a valid workspace name.
69
72
 
70
73
  You can also use a connection string to specify the connection parameters
71
74
  to an Azure Quantum Workspace by calling
@@ -110,6 +113,12 @@ class Workspace:
110
113
  Add the specified value as a prefix to the HTTP User-Agent header
111
114
  when communicating to the Azure Quantum service.
112
115
  """
116
+
117
+ # Internal parameter names
118
+ _FROM_CONNECTION_STRING_PARAM = '_from_connection_string'
119
+ _QUANTUM_ENDPOINT_PARAM = '_quantum_endpoint'
120
+ _MGMT_CLIENT_PARAM = '_mgmt_client'
121
+
113
122
  def __init__(
114
123
  self,
115
124
  subscription_id: Optional[str] = None,
@@ -122,6 +131,14 @@ class Workspace:
122
131
  user_agent: Optional[str] = None,
123
132
  **kwargs: Any,
124
133
  ) -> None:
134
+ # Extract internal params before passing kwargs to WorkspaceConnectionParams
135
+ # Param to track whether the workspace was created from a connection string
136
+ from_connection_string = kwargs.pop(Workspace._FROM_CONNECTION_STRING_PARAM, False)
137
+ # In case from connection string, quantum_endpoint must be passed
138
+ quantum_endpoint = kwargs.pop(Workspace._QUANTUM_ENDPOINT_PARAM, None)
139
+ # Params to pass a mock in tests
140
+ self._mgmt_client = kwargs.pop(Workspace._MGMT_CLIENT_PARAM, None)
141
+
125
142
  connection_params = WorkspaceConnectionParams(
126
143
  location=location,
127
144
  subscription_id=subscription_id,
@@ -129,13 +146,14 @@ class Workspace:
129
146
  workspace_name=name,
130
147
  credential=credential,
131
148
  resource_id=resource_id,
149
+ quantum_endpoint=quantum_endpoint,
132
150
  user_agent=user_agent,
133
151
  **kwargs
134
152
  ).default_from_env_vars()
135
153
 
136
154
  logger.info("Using %s environment.", connection_params.environment)
137
155
 
138
- connection_params.assert_complete()
156
+ connection_params.assert_have_enough_for_discovery()
139
157
 
140
158
  connection_params.on_new_client_request = self._on_new_client_request
141
159
 
@@ -145,6 +163,32 @@ class Workspace:
145
163
  self._resource_group = connection_params.resource_group
146
164
  self._workspace_name = connection_params.workspace_name
147
165
 
166
+ if not self._mgmt_client:
167
+ credential = connection_params.get_credential_or_default()
168
+ self._mgmt_client = WorkspaceMgmtClient(
169
+ credential=credential,
170
+ base_url=connection_params.arm_endpoint,
171
+ user_agent=connection_params.get_full_user_agent(),
172
+ )
173
+
174
+ # pylint: disable=protected-access
175
+ using_connection_string = (
176
+ from_connection_string
177
+ or connection_params._used_connection_string
178
+ )
179
+
180
+ # Populate workspace details from ARG if not using connection string and
181
+ # name is provided but missing subscription and/or resource group
182
+ if not using_connection_string \
183
+ and not connection_params.can_build_resource_id():
184
+ self._mgmt_client.load_workspace_from_arg(connection_params)
185
+
186
+ # Populate workspace details from ARM if not using connection string and not loaded from ARG
187
+ if not using_connection_string and not connection_params.is_complete():
188
+ self._mgmt_client.load_workspace_from_arm(connection_params)
189
+
190
+ connection_params.assert_complete()
191
+
148
192
  # Create QuantumClient
149
193
  self._client = self._create_client()
150
194
 
@@ -277,6 +321,8 @@ class Workspace:
277
321
  :rtype: Workspace
278
322
  """
279
323
  connection_params = WorkspaceConnectionParams(connection_string=connection_string)
324
+ kwargs[cls._FROM_CONNECTION_STRING_PARAM] = True
325
+ kwargs[cls._QUANTUM_ENDPOINT_PARAM] = connection_params.quantum_endpoint
280
326
  return cls(
281
327
  subscription_id=connection_params.subscription_id,
282
328
  resource_group=connection_params.resource_group,
@@ -1023,4 +1069,16 @@ class Workspace:
1023
1069
  return orderby
1024
1070
  else:
1025
1071
  return None
1026
-
1072
+
1073
+ def close(self) -> None:
1074
+ self._mgmt_client.close()
1075
+ self._client.close()
1076
+
1077
+ def __enter__(self) -> Self:
1078
+ self._client.__enter__()
1079
+ self._mgmt_client.__enter__()
1080
+ return self
1081
+
1082
+ def __exit__(self, *exc_details: Any) -> None:
1083
+ self._mgmt_client.__exit__(*exc_details)
1084
+ self._client.__exit__(*exc_details)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azure-quantum
3
- Version: 3.5.0
3
+ Version: 3.5.1.dev0
4
4
  Summary: Python client for Azure Quantum
5
5
  Home-page: https://github.com/microsoft/azure-quantum-python
6
6
  Author: Microsoft
@@ -79,15 +79,14 @@ To get started, visit the following Quickstart guides:
79
79
 
80
80
  ## General usage ##
81
81
 
82
- To connect to your Azure Quantum Workspace, go to the [Azure Portal](https://portal.azure.com), navigate to your Workspace and copy-paste the resource ID and location into the code snippet below.
82
+ To connect to your Azure Quantum Workspace, go to the [Azure Portal](https://portal.azure.com), navigate to your Workspace and copy-paste the resource ID into the code snippet below.
83
83
 
84
84
  ```python
85
85
  from azure.quantum import Workspace
86
86
 
87
- # Enter your Workspace details (resource ID and location) below
87
+ # Enter your Workspace resource ID below
88
88
  workspace = Workspace(
89
- resource_id="",
90
- location=""
89
+ resource_id=""
91
90
  )
92
91
  ```
93
92
 
@@ -9,6 +9,7 @@ requirements.txt
9
9
  setup.py
10
10
  azure/quantum/__init__.py
11
11
  azure/quantum/_constants.py
12
+ azure/quantum/_mgmt_client.py
12
13
  azure/quantum/_workspace_connection_params.py
13
14
  azure/quantum/storage.py
14
15
  azure/quantum/version.py