qi-compute-api-client 0.56.0__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.
Files changed (205) hide show
  1. compute_api_client/__init__.py +219 -0
  2. compute_api_client/api/__init__.py +23 -0
  3. compute_api_client/api/algorithms_api.py +1603 -0
  4. compute_api_client/api/auth_config_api.py +278 -0
  5. compute_api_client/api/backend_api.py +1540 -0
  6. compute_api_client/api/backend_types_api.py +1178 -0
  7. compute_api_client/api/batch_jobs_api.py +2224 -0
  8. compute_api_client/api/commits_api.py +1565 -0
  9. compute_api_client/api/files_api.py +1307 -0
  10. compute_api_client/api/final_results_api.py +847 -0
  11. compute_api_client/api/health_api.py +281 -0
  12. compute_api_client/api/jobs_api.py +1787 -0
  13. compute_api_client/api/languages_api.py +692 -0
  14. compute_api_client/api/members_api.py +1238 -0
  15. compute_api_client/api/metadata_api.py +930 -0
  16. compute_api_client/api/permissions_api.py +1295 -0
  17. compute_api_client/api/projects_api.py +1889 -0
  18. compute_api_client/api/reservations_api.py +1324 -0
  19. compute_api_client/api/results_api.py +1702 -0
  20. compute_api_client/api/teams_api.py +692 -0
  21. compute_api_client/api/transactions_api.py +805 -0
  22. compute_api_client/api/users_api.py +1305 -0
  23. compute_api_client/api_client.py +804 -0
  24. compute_api_client/api_response.py +21 -0
  25. compute_api_client/configuration.py +606 -0
  26. compute_api_client/docs/Algorithm.md +34 -0
  27. compute_api_client/docs/AlgorithmIn.md +33 -0
  28. compute_api_client/docs/AlgorithmType.md +12 -0
  29. compute_api_client/docs/AlgorithmsApi.md +428 -0
  30. compute_api_client/docs/AuthConfig.md +31 -0
  31. compute_api_client/docs/AuthConfigApi.md +71 -0
  32. compute_api_client/docs/Backend.md +35 -0
  33. compute_api_client/docs/BackendApi.md +418 -0
  34. compute_api_client/docs/BackendIn.md +34 -0
  35. compute_api_client/docs/BackendMessage.md +29 -0
  36. compute_api_client/docs/BackendPatch.md +31 -0
  37. compute_api_client/docs/BackendStatus.md +16 -0
  38. compute_api_client/docs/BackendType.md +48 -0
  39. compute_api_client/docs/BackendTypePatch.md +45 -0
  40. compute_api_client/docs/BackendTypesApi.md +289 -0
  41. compute_api_client/docs/BackendWithAuthentication.md +36 -0
  42. compute_api_client/docs/BatchJob.md +39 -0
  43. compute_api_client/docs/BatchJobIn.md +29 -0
  44. compute_api_client/docs/BatchJobStatus.md +18 -0
  45. compute_api_client/docs/BatchJobsApi.md +600 -0
  46. compute_api_client/docs/Commit.md +33 -0
  47. compute_api_client/docs/CommitIn.md +30 -0
  48. compute_api_client/docs/CommitsApi.md +425 -0
  49. compute_api_client/docs/CompilePayload.md +30 -0
  50. compute_api_client/docs/CompileStage.md +18 -0
  51. compute_api_client/docs/Domain.md +14 -0
  52. compute_api_client/docs/File.md +36 -0
  53. compute_api_client/docs/FileIn.md +35 -0
  54. compute_api_client/docs/FilesApi.md +346 -0
  55. compute_api_client/docs/FinalResult.md +32 -0
  56. compute_api_client/docs/FinalResultIn.md +30 -0
  57. compute_api_client/docs/FinalResultsApi.md +248 -0
  58. compute_api_client/docs/HTTPBadRequestError.md +29 -0
  59. compute_api_client/docs/HTTPNotFoundError.md +29 -0
  60. compute_api_client/docs/HTTPValidationError.md +29 -0
  61. compute_api_client/docs/HealthApi.md +72 -0
  62. compute_api_client/docs/Job.md +42 -0
  63. compute_api_client/docs/JobIn.md +32 -0
  64. compute_api_client/docs/JobPatch.md +34 -0
  65. compute_api_client/docs/JobStatus.md +18 -0
  66. compute_api_client/docs/JobsApi.md +460 -0
  67. compute_api_client/docs/Language.md +31 -0
  68. compute_api_client/docs/LanguagesApi.md +177 -0
  69. compute_api_client/docs/LocationInner.md +27 -0
  70. compute_api_client/docs/Member.md +33 -0
  71. compute_api_client/docs/MemberId.md +28 -0
  72. compute_api_client/docs/MemberIn.md +32 -0
  73. compute_api_client/docs/MembersApi.md +331 -0
  74. compute_api_client/docs/PageAlgorithm.md +33 -0
  75. compute_api_client/docs/PageBackend.md +33 -0
  76. compute_api_client/docs/PageBackendType.md +33 -0
  77. compute_api_client/docs/PageBatchJob.md +33 -0
  78. compute_api_client/docs/PageCommit.md +33 -0
  79. compute_api_client/docs/PageFile.md +33 -0
  80. compute_api_client/docs/PageJob.md +33 -0
  81. compute_api_client/docs/PageLanguage.md +33 -0
  82. compute_api_client/docs/PageMember.md +33 -0
  83. compute_api_client/docs/PageMetadata.md +32 -0
  84. compute_api_client/docs/PagePermission.md +33 -0
  85. compute_api_client/docs/PagePermissionGroup.md +33 -0
  86. compute_api_client/docs/PageProject.md +33 -0
  87. compute_api_client/docs/PageReservation.md +33 -0
  88. compute_api_client/docs/PageResult.md +33 -0
  89. compute_api_client/docs/PageTeam.md +33 -0
  90. compute_api_client/docs/PageTransaction.md +33 -0
  91. compute_api_client/docs/PageUser.md +33 -0
  92. compute_api_client/docs/Permission.md +31 -0
  93. compute_api_client/docs/PermissionGroup.md +30 -0
  94. compute_api_client/docs/PermissionsApi.md +340 -0
  95. compute_api_client/docs/Project.md +34 -0
  96. compute_api_client/docs/ProjectIn.md +32 -0
  97. compute_api_client/docs/ProjectPatch.md +32 -0
  98. compute_api_client/docs/ProjectsApi.md +502 -0
  99. compute_api_client/docs/Reservation.md +35 -0
  100. compute_api_client/docs/ReservationIn.md +32 -0
  101. compute_api_client/docs/ReservationsApi.md +341 -0
  102. compute_api_client/docs/Result.md +36 -0
  103. compute_api_client/docs/ResultIn.md +34 -0
  104. compute_api_client/docs/ResultsApi.md +439 -0
  105. compute_api_client/docs/Role.md +12 -0
  106. compute_api_client/docs/ShareType.md +14 -0
  107. compute_api_client/docs/Team.md +32 -0
  108. compute_api_client/docs/TeamsApi.md +177 -0
  109. compute_api_client/docs/Transaction.md +35 -0
  110. compute_api_client/docs/TransactionDomain.md +28 -0
  111. compute_api_client/docs/TransactionsApi.md +190 -0
  112. compute_api_client/docs/User.md +36 -0
  113. compute_api_client/docs/UserIn.md +35 -0
  114. compute_api_client/docs/UsersApi.md +338 -0
  115. compute_api_client/docs/ValidationError.md +31 -0
  116. compute_api_client/docs/ValidationErrorLocInner.md +28 -0
  117. compute_api_client/exceptions.py +216 -0
  118. compute_api_client/models/__init__.py +84 -0
  119. compute_api_client/models/algorithm.py +105 -0
  120. compute_api_client/models/algorithm_in.py +103 -0
  121. compute_api_client/models/algorithm_type.py +37 -0
  122. compute_api_client/models/auth_config.py +91 -0
  123. compute_api_client/models/backend.py +106 -0
  124. compute_api_client/models/backend_in.py +104 -0
  125. compute_api_client/models/backend_message.py +87 -0
  126. compute_api_client/models/backend_patch.py +112 -0
  127. compute_api_client/models/backend_status.py +39 -0
  128. compute_api_client/models/backend_type.py +145 -0
  129. compute_api_client/models/backend_type_patch.py +205 -0
  130. compute_api_client/models/backend_with_authentication.py +108 -0
  131. compute_api_client/models/batch_job.py +130 -0
  132. compute_api_client/models/batch_job_in.py +87 -0
  133. compute_api_client/models/batch_job_status.py +40 -0
  134. compute_api_client/models/commit.py +97 -0
  135. compute_api_client/models/commit_in.py +89 -0
  136. compute_api_client/models/compile_payload.py +95 -0
  137. compute_api_client/models/compile_stage.py +40 -0
  138. compute_api_client/models/domain.py +38 -0
  139. compute_api_client/models/file.py +108 -0
  140. compute_api_client/models/file_in.py +106 -0
  141. compute_api_client/models/final_result.py +94 -0
  142. compute_api_client/models/final_result_in.py +89 -0
  143. compute_api_client/models/http_bad_request_error.py +87 -0
  144. compute_api_client/models/http_not_found_error.py +87 -0
  145. compute_api_client/models/http_validation_error.py +95 -0
  146. compute_api_client/models/job.py +131 -0
  147. compute_api_client/models/job_in.py +98 -0
  148. compute_api_client/models/job_patch.py +99 -0
  149. compute_api_client/models/job_status.py +40 -0
  150. compute_api_client/models/language.py +92 -0
  151. compute_api_client/models/location_inner.py +144 -0
  152. compute_api_client/models/member.py +96 -0
  153. compute_api_client/models/member_id.py +144 -0
  154. compute_api_client/models/member_in.py +94 -0
  155. compute_api_client/models/metadata.py +93 -0
  156. compute_api_client/models/metadata_in.py +94 -0
  157. compute_api_client/models/page_algorithm.py +104 -0
  158. compute_api_client/models/page_backend.py +104 -0
  159. compute_api_client/models/page_backend_type.py +104 -0
  160. compute_api_client/models/page_batch_job.py +104 -0
  161. compute_api_client/models/page_commit.py +104 -0
  162. compute_api_client/models/page_file.py +104 -0
  163. compute_api_client/models/page_job.py +104 -0
  164. compute_api_client/models/page_language.py +104 -0
  165. compute_api_client/models/page_member.py +104 -0
  166. compute_api_client/models/page_metadata.py +125 -0
  167. compute_api_client/models/page_permission.py +104 -0
  168. compute_api_client/models/page_permission_group.py +104 -0
  169. compute_api_client/models/page_project.py +104 -0
  170. compute_api_client/models/page_reservation.py +104 -0
  171. compute_api_client/models/page_result.py +104 -0
  172. compute_api_client/models/page_team.py +104 -0
  173. compute_api_client/models/page_transaction.py +104 -0
  174. compute_api_client/models/page_user.py +104 -0
  175. compute_api_client/models/permission.py +92 -0
  176. compute_api_client/models/permission_group.py +90 -0
  177. compute_api_client/models/project.py +99 -0
  178. compute_api_client/models/project_in.py +94 -0
  179. compute_api_client/models/project_patch.py +114 -0
  180. compute_api_client/models/reservation.py +105 -0
  181. compute_api_client/models/reservation_in.py +94 -0
  182. compute_api_client/models/result.py +122 -0
  183. compute_api_client/models/result_in.py +117 -0
  184. compute_api_client/models/role.py +37 -0
  185. compute_api_client/models/share_type.py +38 -0
  186. compute_api_client/models/team.py +94 -0
  187. compute_api_client/models/transaction.py +117 -0
  188. compute_api_client/models/transaction_domain.py +142 -0
  189. compute_api_client/models/user.py +102 -0
  190. compute_api_client/models/user_in.py +100 -0
  191. compute_api_client/models/validation_error.py +99 -0
  192. compute_api_client/models/validation_error_loc_inner.py +138 -0
  193. compute_api_client/rest.py +213 -0
  194. qi2_shared/__init__.py +0 -0
  195. qi2_shared/authentication.py +66 -0
  196. qi2_shared/client.py +52 -0
  197. qi2_shared/hybrid/__init__.py +0 -0
  198. qi2_shared/hybrid/quantum_interface.py +45 -0
  199. qi2_shared/pagination.py +44 -0
  200. qi2_shared/settings.py +68 -0
  201. qi2_shared/utils.py +13 -0
  202. qi_compute_api_client-0.56.0.dist-info/METADATA +266 -0
  203. qi_compute_api_client-0.56.0.dist-info/RECORD +205 -0
  204. qi_compute_api_client-0.56.0.dist-info/WHEEL +4 -0
  205. qi_compute_api_client-0.56.0.dist-info/licenses/LICENSE.md +142 -0
@@ -0,0 +1,213 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ Quantum Inspire 2
5
+
6
+ No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
7
+
8
+ The version of the OpenAPI document: 0.1.0
9
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
10
+
11
+ Do not edit the class manually.
12
+ """ # noqa: E501
13
+
14
+
15
+ import io
16
+ import json
17
+ import re
18
+ import ssl
19
+ from typing import Optional, Union
20
+
21
+ import aiohttp
22
+ import aiohttp_retry
23
+
24
+ from compute_api_client.exceptions import ApiException, ApiValueError
25
+
26
+ RESTResponseType = aiohttp.ClientResponse
27
+
28
+ ALLOW_RETRY_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE'})
29
+
30
+ class RESTResponse(io.IOBase):
31
+
32
+ def __init__(self, resp) -> None:
33
+ self.response = resp
34
+ self.status = resp.status
35
+ self.reason = resp.reason
36
+ self.data = None
37
+
38
+ async def read(self):
39
+ if self.data is None:
40
+ self.data = await self.response.read()
41
+ return self.data
42
+
43
+ def getheaders(self):
44
+ """Returns a CIMultiDictProxy of the response headers."""
45
+ return self.response.headers
46
+
47
+ def getheader(self, name, default=None):
48
+ """Returns a given response header."""
49
+ return self.response.headers.get(name, default)
50
+
51
+
52
+ class RESTClientObject:
53
+
54
+ def __init__(self, configuration) -> None:
55
+
56
+ # maxsize is number of requests to host that are allowed in parallel
57
+ self.maxsize = configuration.connection_pool_maxsize
58
+
59
+ self.ssl_context = ssl.create_default_context(
60
+ cafile=configuration.ssl_ca_cert,
61
+ cadata=configuration.ca_cert_data,
62
+ )
63
+ if configuration.cert_file:
64
+ self.ssl_context.load_cert_chain(
65
+ configuration.cert_file, keyfile=configuration.key_file
66
+ )
67
+
68
+ if not configuration.verify_ssl:
69
+ self.ssl_context.check_hostname = False
70
+ self.ssl_context.verify_mode = ssl.CERT_NONE
71
+
72
+ self.proxy = configuration.proxy
73
+ self.proxy_headers = configuration.proxy_headers
74
+
75
+ self.retries = configuration.retries
76
+
77
+ self.pool_manager: Optional[aiohttp.ClientSession] = None
78
+ self.retry_client: Optional[aiohttp_retry.RetryClient] = None
79
+
80
+ async def close(self) -> None:
81
+ if self.pool_manager:
82
+ await self.pool_manager.close()
83
+ if self.retry_client is not None:
84
+ await self.retry_client.close()
85
+
86
+ async def request(
87
+ self,
88
+ method,
89
+ url,
90
+ headers=None,
91
+ body=None,
92
+ post_params=None,
93
+ _request_timeout=None
94
+ ):
95
+ """Execute request
96
+
97
+ :param method: http request method
98
+ :param url: http request url
99
+ :param headers: http request headers
100
+ :param body: request json body, for `application/json`
101
+ :param post_params: request post parameters,
102
+ `application/x-www-form-urlencoded`
103
+ and `multipart/form-data`
104
+ :param _request_timeout: timeout setting for this request. If one
105
+ number provided, it will be total request
106
+ timeout. It can also be a pair (tuple) of
107
+ (connection, read) timeouts.
108
+ """
109
+ method = method.upper()
110
+ assert method in [
111
+ 'GET',
112
+ 'HEAD',
113
+ 'DELETE',
114
+ 'POST',
115
+ 'PUT',
116
+ 'PATCH',
117
+ 'OPTIONS'
118
+ ]
119
+
120
+ if post_params and body:
121
+ raise ApiValueError(
122
+ "body parameter cannot be used with post_params parameter."
123
+ )
124
+
125
+ post_params = post_params or {}
126
+ headers = headers or {}
127
+ # url already contains the URL query string
128
+ timeout = _request_timeout or 5 * 60
129
+
130
+ if 'Content-Type' not in headers:
131
+ headers['Content-Type'] = 'application/json'
132
+
133
+ args = {
134
+ "method": method,
135
+ "url": url,
136
+ "timeout": timeout,
137
+ "headers": headers
138
+ }
139
+
140
+ if self.proxy:
141
+ args["proxy"] = self.proxy
142
+ if self.proxy_headers:
143
+ args["proxy_headers"] = self.proxy_headers
144
+
145
+ # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
146
+ if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
147
+ if re.search('json', headers['Content-Type'], re.IGNORECASE):
148
+ if body is not None:
149
+ body = json.dumps(body)
150
+ args["data"] = body
151
+ elif headers['Content-Type'] == 'application/x-www-form-urlencoded':
152
+ args["data"] = aiohttp.FormData(post_params)
153
+ elif headers['Content-Type'] == 'multipart/form-data':
154
+ # must del headers['Content-Type'], or the correct
155
+ # Content-Type which generated by aiohttp
156
+ del headers['Content-Type']
157
+ data = aiohttp.FormData()
158
+ for param in post_params:
159
+ k, v = param
160
+ if isinstance(v, tuple) and len(v) == 3:
161
+ data.add_field(
162
+ k,
163
+ value=v[1],
164
+ filename=v[0],
165
+ content_type=v[2]
166
+ )
167
+ else:
168
+ # Ensures that dict objects are serialized
169
+ if isinstance(v, dict):
170
+ v = json.dumps(v)
171
+ elif isinstance(v, int):
172
+ v = str(v)
173
+ data.add_field(k, v)
174
+ args["data"] = data
175
+
176
+ # Pass a `bytes` or `str` parameter directly in the body to support
177
+ # other content types than Json when `body` argument is provided
178
+ # in serialized form
179
+ elif isinstance(body, str) or isinstance(body, bytes):
180
+ args["data"] = body
181
+ else:
182
+ # Cannot generate the request from given parameters
183
+ msg = """Cannot prepare a request message for provided
184
+ arguments. Please check that your arguments match
185
+ declared content type."""
186
+ raise ApiException(status=0, reason=msg)
187
+
188
+ pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient]
189
+
190
+ # https pool manager
191
+ if self.pool_manager is None:
192
+ self.pool_manager = aiohttp.ClientSession(
193
+ connector=aiohttp.TCPConnector(limit=self.maxsize, ssl=self.ssl_context),
194
+ trust_env=True,
195
+ )
196
+ pool_manager = self.pool_manager
197
+
198
+ if self.retries is not None and method in ALLOW_RETRY_METHODS:
199
+ if self.retry_client is None:
200
+ self.retry_client = aiohttp_retry.RetryClient(
201
+ client_session=self.pool_manager,
202
+ retry_options=aiohttp_retry.ExponentialRetry(
203
+ attempts=self.retries,
204
+ factor=2.0,
205
+ start_timeout=0.1,
206
+ max_timeout=120.0
207
+ )
208
+ )
209
+ pool_manager = self.retry_client
210
+
211
+ r = await pool_manager.request(**args)
212
+
213
+ return RESTResponse(r)
qi2_shared/__init__.py ADDED
File without changes
@@ -0,0 +1,66 @@
1
+ import time
2
+ from typing import Any, Tuple, cast
3
+
4
+ import requests
5
+
6
+ from qi2_shared.settings import ApiSettings, TokenInfo, Url
7
+
8
+
9
+ class AuthorisationError(Exception):
10
+ """Indicates that the authorisation permanently went wrong."""
11
+
12
+ pass
13
+
14
+
15
+ class IdentityProvider:
16
+ """Class for interfacing with the IdentityProvider."""
17
+
18
+ def __init__(self, well_known_endpoint: str):
19
+ self._well_known_endpoint = well_known_endpoint
20
+ self._token_endpoint, self._device_endpoint = self._get_endpoints()
21
+ self._headers = {"Content-Type": "application/x-www-form-urlencoded"}
22
+
23
+ def _get_endpoints(self) -> Tuple[str, str]:
24
+ response = requests.get(self._well_known_endpoint)
25
+ response.raise_for_status()
26
+ config = response.json()
27
+ return config["token_endpoint"], config["device_authorization_endpoint"]
28
+
29
+ def refresh_access_token(self, client_id: str, refresh_token: str) -> dict[str, Any]:
30
+ data = {
31
+ "grant_type": "refresh_token",
32
+ "client_id": client_id,
33
+ "refresh_token": refresh_token,
34
+ }
35
+ response = requests.post(self._token_endpoint, headers=self._headers, data=data)
36
+ response.raise_for_status()
37
+ return cast(dict[str, Any], response.json())
38
+
39
+
40
+ class OauthDeviceSession:
41
+ """Class for storing OAuth session information and refreshing tokens when needed."""
42
+
43
+ def __init__(self, host: Url, settings: ApiSettings, identity_provider: IdentityProvider):
44
+ self._api_settings = settings
45
+ _auth_settings = settings.auths[host]
46
+ self._host = host
47
+ self._client_id = _auth_settings.client_id
48
+ self._token_info = _auth_settings.tokens
49
+ self._refresh_time_reduction = 5 # the number of seconds to refresh the expiration time
50
+ self._identity_provider = identity_provider
51
+
52
+ def refresh(self) -> TokenInfo:
53
+ if self._token_info is None:
54
+ raise AuthorisationError("You should authenticate first before you can refresh")
55
+
56
+ if self._token_info.access_expires_at > time.time() + self._refresh_time_reduction:
57
+ return self._token_info
58
+
59
+ try:
60
+ self._token_info = TokenInfo(
61
+ **self._identity_provider.refresh_access_token(self._client_id, self._token_info.refresh_token)
62
+ )
63
+ self._api_settings.store_tokens(self._host, self._token_info)
64
+ return self._token_info
65
+ except requests.HTTPError as e:
66
+ raise AuthorisationError(f"An error occurred during token refresh: {e}")
qi2_shared/client.py ADDED
@@ -0,0 +1,52 @@
1
+ from typing import Any, Optional
2
+
3
+ import compute_api_client
4
+
5
+ from qi2_shared.authentication import IdentityProvider, OauthDeviceSession
6
+ from qi2_shared.settings import ApiSettings
7
+
8
+
9
+ class Configuration(compute_api_client.Configuration): # type: ignore[misc]
10
+ """Original Configuration class in compute_api_client does not handle refreshing bearer tokens, so we need to add
11
+ some functionality."""
12
+
13
+ def __init__(self, host: str, oauth_session: OauthDeviceSession, **kwargs: Any):
14
+ self._oauth_session = oauth_session
15
+ super().__init__(host=host, **kwargs)
16
+
17
+ def auth_settings(self) -> Any:
18
+ token_info = self._oauth_session.refresh()
19
+ self.access_token = token_info.access_token
20
+ return super().auth_settings()
21
+
22
+
23
+ _config: Optional[Configuration] = None
24
+
25
+
26
+ def connect() -> None:
27
+ """Set connection configuration for the Quantum Inspire API.
28
+
29
+ Call after logging in with the CLI. Will remove old configuration.
30
+ """
31
+ global _config
32
+ settings = ApiSettings.from_config_file()
33
+
34
+ tokens = settings.auths[settings.default_host].tokens
35
+
36
+ if tokens is None:
37
+ raise ValueError("No access token found for the default host. Please connect to Quantum Inspire using the CLI.")
38
+
39
+ host = settings.default_host
40
+ _config = Configuration(
41
+ host=host,
42
+ oauth_session=OauthDeviceSession(host, settings, IdentityProvider(settings.auths[host].well_known_endpoint)),
43
+ )
44
+
45
+
46
+ def config() -> Configuration:
47
+ global _config
48
+ if _config is None:
49
+ connect()
50
+
51
+ assert _config is not None
52
+ return _config
File without changes
@@ -0,0 +1,45 @@
1
+ """Quantum Inspire SDK.
2
+
3
+ Copyright 2022 QuTech Delft
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
6
+ License. You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
11
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
12
+ language governing permissions and limitations under the License.
13
+ """
14
+
15
+ from abc import ABC, abstractmethod
16
+ from typing import Any, Dict, List, Optional
17
+
18
+ from compute_api_client.models.backend_type import BackendType
19
+ from pydantic import BaseModel
20
+
21
+
22
+ class ExecuteCircuitResult(BaseModel):
23
+ """Result of executing a quantum circuit."""
24
+
25
+ results: Dict[str, int]
26
+ raw_data: Optional[List[str]]
27
+ shots_requested: int
28
+ shots_done: int
29
+
30
+
31
+ class QuantumInterface(ABC):
32
+ """Interface for running quantum circuits from hybrid algorithms."""
33
+
34
+ # pylint: disable = R0903
35
+ # Too few public methods (1/2) (too-few-public-methods)
36
+
37
+ results: List[Dict[str, Any]]
38
+ backend_type: BackendType
39
+
40
+ @abstractmethod
41
+ def execute_circuit(
42
+ self, circuit: str, number_of_shots: int, raw_data_enabled: bool = False
43
+ ) -> ExecuteCircuitResult:
44
+ """Execute a quantum circuit."""
45
+ raise NotImplementedError
@@ -0,0 +1,44 @@
1
+ from typing import Any, Awaitable, Callable, Generic, List, Optional, TypeVar, Union, cast
2
+
3
+ from pydantic import BaseModel, Field
4
+ from typing_extensions import Annotated
5
+
6
+ PageType = TypeVar("PageType")
7
+ ItemType = TypeVar("ItemType")
8
+
9
+
10
+ class PageInterface(BaseModel, Generic[ItemType]):
11
+ """The page models in the generated API client don't inherit from a common base class, so we have to trick the
12
+ typing system a bit with this fake base class."""
13
+
14
+ items: List[ItemType]
15
+ total: Optional[Annotated[int, Field(strict=True, ge=0)]]
16
+ page: Optional[Annotated[int, Field(strict=True, ge=1)]]
17
+ size: Optional[Annotated[int, Field(strict=True, ge=1)]]
18
+ pages: Optional[Annotated[int, Field(strict=True, ge=0)]] = None
19
+
20
+
21
+ class PageReader(Generic[PageType, ItemType]):
22
+ """Helper class for reading fastapi-pagination style pages returned by the compute_api_client."""
23
+
24
+ async def get_all(self, api_call: Callable[..., Awaitable[PageType]], **kwargs: Any) -> List[ItemType]:
25
+ """Get all items from an API call that supports paging."""
26
+ items: List[ItemType] = []
27
+ page = 1
28
+
29
+ while True:
30
+ response = cast(PageInterface[ItemType], await api_call(page=page, **kwargs))
31
+
32
+ items.extend(response.items)
33
+ page += 1
34
+ if response.pages is None or page > response.pages:
35
+ break
36
+ return items
37
+
38
+ async def get_single(self, api_call: Callable[..., Awaitable[PageType]], **kwargs: Any) -> Union[ItemType, None]:
39
+ """Get a single item from an API call that supports paging."""
40
+ response = cast(PageInterface[ItemType], await api_call(**kwargs))
41
+ if len(response.items) > 1:
42
+ raise RuntimeError(f"Response contains more than one item -> {kwargs}.")
43
+
44
+ return response.items[0] if response.items else None
qi2_shared/settings.py ADDED
@@ -0,0 +1,68 @@
1
+ """Module containing the handler for the Quantum Inspire persistent configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from pathlib import Path
7
+ from typing import Dict, Optional
8
+
9
+ from pydantic import BaseModel, BeforeValidator, Field, HttpUrl
10
+ from typing_extensions import Annotated
11
+
12
+ Url = Annotated[str, BeforeValidator(lambda value: str(HttpUrl(value)).rstrip("/"))]
13
+ API_SETTINGS_FILE = Path.joinpath(Path.home(), ".quantuminspire", "config.json")
14
+
15
+
16
+ class TokenInfo(BaseModel):
17
+ """A pydantic model for storing all information regarding oauth access and refresh tokens."""
18
+
19
+ access_token: str
20
+ expires_in: int # [s]
21
+ refresh_token: str
22
+ refresh_expires_in: Optional[int] = None # [s]
23
+ generated_at: float = Field(default_factory=time.time)
24
+
25
+ @property
26
+ def access_expires_at(self) -> float:
27
+ """Unix timestamp containing the time when the access token will expire."""
28
+ return self.generated_at + self.expires_in
29
+
30
+
31
+ class AuthSettings(BaseModel):
32
+ """Pydantic model for storing all auth related settings for a given host."""
33
+
34
+ client_id: str
35
+ code_challenge_method: str
36
+ code_verifyer_length: int
37
+ well_known_endpoint: Url
38
+ tokens: Optional[TokenInfo]
39
+ team_member_id: Optional[int]
40
+
41
+
42
+ class ApiSettings(BaseModel):
43
+ """The settings class for the Quantum Inspire persistent configuration."""
44
+
45
+ auths: Dict[Url, AuthSettings]
46
+ default_host: Url
47
+
48
+ def store_tokens(self, host: Url, tokens: TokenInfo, path: Path = API_SETTINGS_FILE) -> None:
49
+ """Stores the team_member_id, access and refresh tokens in the config.json file.
50
+
51
+ Args:
52
+ host: The hostname of the API for which the tokens are intended.
53
+ tokens: OAuth access and refresh tokens.
54
+ path: The path to the config.json file. Defaults to API_SETTINGS_FILE.
55
+ Returns:
56
+ None
57
+ """
58
+ self.auths[host].tokens = tokens
59
+ path.write_text(self.model_dump_json(indent=2))
60
+
61
+ @classmethod
62
+ def from_config_file(cls, path: Path = API_SETTINGS_FILE) -> ApiSettings:
63
+ """Load the configuration from a file."""
64
+ if not path.is_file():
65
+ raise FileNotFoundError("No configuration file found. Please connect to Quantum Inspire using the CLI.")
66
+
67
+ api_settings = path.read_text()
68
+ return ApiSettings.model_validate_json(api_settings)
qi2_shared/utils.py ADDED
@@ -0,0 +1,13 @@
1
+ import asyncio
2
+ import concurrent
3
+ from typing import Any, Coroutine
4
+
5
+
6
+ def run_async(async_function: Coroutine[Any, Any, Any]) -> Any:
7
+ try:
8
+ _ = asyncio.get_running_loop()
9
+ except RuntimeError:
10
+ return asyncio.run(async_function)
11
+
12
+ with concurrent.futures.ThreadPoolExecutor() as executor:
13
+ return executor.submit(asyncio.run, async_function).result()