phenostore-sdk-python 0.1.1__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 (72) hide show
  1. phenostore/__init__.py +26 -0
  2. phenostore/_auth.py +94 -0
  3. phenostore/client.py +276 -0
  4. phenostore/errors.py +93 -0
  5. phenostore/gen/__init__.py +8 -0
  6. phenostore/gen/api/__init__.py +1 -0
  7. phenostore/gen/api/bulk/__init__.py +1 -0
  8. phenostore/gen/api/bulk/get_bulk_status.py +194 -0
  9. phenostore/gen/api/bulk/submit_bulk.py +223 -0
  10. phenostore/gen/api/fhir/__init__.py +1 -0
  11. phenostore/gen/api/fhir/conditional_delete_resource.py +277 -0
  12. phenostore/gen/api/fhir/create_resource.py +257 -0
  13. phenostore/gen/api/fhir/delete_resource.py +212 -0
  14. phenostore/gen/api/fhir/get_docref.py +281 -0
  15. phenostore/gen/api/fhir/get_metadata.py +185 -0
  16. phenostore/gen/api/fhir/get_resource_history.py +240 -0
  17. phenostore/gen/api/fhir/get_smart_configuration.py +169 -0
  18. phenostore/gen/api/fhir/patch_resource.py +282 -0
  19. phenostore/gen/api/fhir/post_docref.py +200 -0
  20. phenostore/gen/api/fhir/process_bundle.py +201 -0
  21. phenostore/gen/api/fhir/read_resource.py +214 -0
  22. phenostore/gen/api/fhir/read_resource_version.py +228 -0
  23. phenostore/gen/api/fhir/search_resources.py +390 -0
  24. phenostore/gen/api/fhir/search_resources_post.py +228 -0
  25. phenostore/gen/api/fhir/update_resource.py +281 -0
  26. phenostore/gen/api/fhir/validate_resource.py +217 -0
  27. phenostore/gen/api/o_auth/__init__.py +1 -0
  28. phenostore/gen/api/o_auth/issue_token.py +186 -0
  29. phenostore/gen/api/system/__init__.py +1 -0
  30. phenostore/gen/api/system/get_health.py +128 -0
  31. phenostore/gen/client.py +280 -0
  32. phenostore/gen/errors.py +16 -0
  33. phenostore/gen/models/__init__.py +71 -0
  34. phenostore/gen/models/bulk_operation_accepted.py +79 -0
  35. phenostore/gen/models/bulk_operation_accepted_status.py +8 -0
  36. phenostore/gen/models/bulk_operation_status.py +122 -0
  37. phenostore/gen/models/bulk_operation_status_code.py +11 -0
  38. phenostore/gen/models/bundle.py +132 -0
  39. phenostore/gen/models/bundle_entry.py +128 -0
  40. phenostore/gen/models/bundle_entry_request.py +70 -0
  41. phenostore/gen/models/bundle_entry_response.py +97 -0
  42. phenostore/gen/models/bundle_entry_search.py +69 -0
  43. phenostore/gen/models/bundle_entry_search_mode.py +9 -0
  44. phenostore/gen/models/bundle_link.py +69 -0
  45. phenostore/gen/models/bundle_resource_type.py +8 -0
  46. phenostore/gen/models/bundle_type.py +11 -0
  47. phenostore/gen/models/fhir_resource.py +47 -0
  48. phenostore/gen/models/health_response.py +63 -0
  49. phenostore/gen/models/health_response_status.py +8 -0
  50. phenostore/gen/models/issue_severity.py +11 -0
  51. phenostore/gen/models/issue_token_body.py +91 -0
  52. phenostore/gen/models/issue_token_body_grant_type.py +8 -0
  53. phenostore/gen/models/json_patch_item.py +90 -0
  54. phenostore/gen/models/json_patch_op.py +13 -0
  55. phenostore/gen/models/o_auth_error.py +69 -0
  56. phenostore/gen/models/operation_outcome.py +85 -0
  57. phenostore/gen/models/operation_outcome_issue.py +92 -0
  58. phenostore/gen/models/operation_outcome_resource_type.py +8 -0
  59. phenostore/gen/models/post_docref_body.py +124 -0
  60. phenostore/gen/models/resource_type.py +47 -0
  61. phenostore/gen/models/search_resources_post_body.py +155 -0
  62. phenostore/gen/models/search_summary_mode.py +12 -0
  63. phenostore/gen/models/search_total_mode.py +10 -0
  64. phenostore/gen/models/smart_configuration.py +111 -0
  65. phenostore/gen/models/token_response.py +87 -0
  66. phenostore/gen/models/token_response_token_type.py +8 -0
  67. phenostore/gen/types.py +54 -0
  68. phenostore/py.typed +0 -0
  69. phenostore_sdk_python-0.1.1.dist-info/METADATA +196 -0
  70. phenostore_sdk_python-0.1.1.dist-info/RECORD +72 -0
  71. phenostore_sdk_python-0.1.1.dist-info/WHEEL +4 -0
  72. phenostore_sdk_python-0.1.1.dist-info/licenses/LICENSE +21 -0
phenostore/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """Python SDK for the Phenostore FHIR data API."""
2
+
3
+ from .client import AsyncClient, Client
4
+ from .errors import (
5
+ AuthenticationError,
6
+ InvalidResponseError,
7
+ OperationOutcomeError,
8
+ is_conflict,
9
+ is_forbidden,
10
+ is_gone,
11
+ is_not_found,
12
+ )
13
+
14
+ __version__ = "0.1.1" # x-release-please-version
15
+
16
+ __all__ = [
17
+ "AsyncClient",
18
+ "AuthenticationError",
19
+ "Client",
20
+ "InvalidResponseError",
21
+ "OperationOutcomeError",
22
+ "is_conflict",
23
+ "is_forbidden",
24
+ "is_gone",
25
+ "is_not_found",
26
+ ]
phenostore/_auth.py ADDED
@@ -0,0 +1,94 @@
1
+ """HTTPX OAuth2 client credentials authentication for both execution modes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import math
7
+ import threading
8
+ import time
9
+ from collections.abc import AsyncGenerator, Generator, Sequence
10
+
11
+ import httpx
12
+
13
+ from .errors import AuthenticationError
14
+
15
+
16
+ class ClientCredentialsAuth(httpx.Auth):
17
+ requires_response_body = True
18
+
19
+ def __init__(
20
+ self, token_url: str, client_id: str, client_secret: str, scopes: Sequence[str]
21
+ ) -> None:
22
+ self._token_url = token_url
23
+ self._client_id = client_id
24
+ self._client_secret = client_secret
25
+ self._scopes = tuple(scopes)
26
+ self._token = ""
27
+ self._expires_at = 0.0
28
+ self._sync_lock = threading.Lock()
29
+ self._async_lock = asyncio.Lock()
30
+
31
+ def _expired(self) -> bool:
32
+ return not self._token or time.monotonic() >= self._expires_at
33
+
34
+ def _token_request(self, request: httpx.Request) -> httpx.Request:
35
+ data = {
36
+ "grant_type": "client_credentials",
37
+ "client_id": self._client_id,
38
+ "client_secret": self._client_secret,
39
+ }
40
+ if self._scopes:
41
+ data["scope"] = " ".join(self._scopes)
42
+ return httpx.Request(
43
+ "POST",
44
+ self._token_url,
45
+ data=data,
46
+ headers={"Accept": "application/json"},
47
+ extensions={"timeout": request.extensions.get("timeout", {})},
48
+ )
49
+
50
+ def _receive_token(self, response: httpx.Response) -> None:
51
+ if response.status_code != 200:
52
+ raise AuthenticationError(response)
53
+ try:
54
+ data = response.json()
55
+ token = data["access_token"]
56
+ token_type = data["token_type"]
57
+ lifetime = float(data["expires_in"])
58
+ if (
59
+ not isinstance(token, str)
60
+ or not token
61
+ or not token.isascii()
62
+ or any(char.isspace() or ord(char) < 32 or ord(char) == 127 for char in token)
63
+ or not isinstance(token_type, str)
64
+ or token_type.lower() != "bearer"
65
+ or not math.isfinite(lifetime)
66
+ or lifetime <= 0
67
+ ):
68
+ raise ValueError("invalid token")
69
+ except (ValueError, TypeError, KeyError, OverflowError):
70
+ raise AuthenticationError(response, "invalid token response") from None
71
+ self._token = token
72
+ self._expires_at = time.monotonic() + lifetime - min(10, lifetime / 10)
73
+
74
+ def sync_auth_flow(
75
+ self, request: httpx.Request
76
+ ) -> Generator[httpx.Request, httpx.Response, None]:
77
+ with self._sync_lock:
78
+ if self._expired():
79
+ response = yield self._token_request(request)
80
+ response.read()
81
+ self._receive_token(response)
82
+ request.headers["Authorization"] = f"Bearer {self._token}"
83
+ yield request
84
+
85
+ async def async_auth_flow(
86
+ self, request: httpx.Request
87
+ ) -> AsyncGenerator[httpx.Request, httpx.Response]:
88
+ async with self._async_lock:
89
+ if self._expired():
90
+ response = yield self._token_request(request)
91
+ await response.aread()
92
+ self._receive_token(response)
93
+ request.headers["Authorization"] = f"Bearer {self._token}"
94
+ yield request
phenostore/client.py ADDED
@@ -0,0 +1,276 @@
1
+ """Small convenience layer over generated request builders and HTTPX."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ssl
6
+ from collections.abc import Mapping, Sequence
7
+ from typing import Any, Self, cast
8
+
9
+ import httpx
10
+
11
+ from ._auth import ClientCredentialsAuth
12
+ from .errors import OperationOutcomeError, _json_object
13
+ from .gen.api.fhir import (
14
+ create_resource,
15
+ delete_resource,
16
+ process_bundle,
17
+ read_resource,
18
+ search_resources,
19
+ update_resource,
20
+ )
21
+ from .gen.client import Client as GeneratedClient
22
+ from .gen.models import FhirResource, ResourceType
23
+ from .gen.types import UNSET
24
+
25
+ JSON = dict[str, Any]
26
+ SearchParams = Mapping[str, str | int | Sequence[str]] | Sequence[tuple[str, str | int]]
27
+
28
+
29
+ class _BaseClient:
30
+ def __init__(
31
+ self,
32
+ base_url: str,
33
+ client_id: str,
34
+ client_secret: str,
35
+ tenant: str,
36
+ store: str,
37
+ *,
38
+ scopes: Sequence[str] = (),
39
+ timeout: float | httpx.Timeout = 30.0,
40
+ verify: bool | ssl.SSLContext = True,
41
+ transport: httpx.BaseTransport | httpx.AsyncBaseTransport | None = None,
42
+ ) -> None:
43
+ url = httpx.URL(base_url)
44
+ if (
45
+ url.scheme not in ("http", "https")
46
+ or not url.host
47
+ or url.query
48
+ or url.fragment
49
+ or url.userinfo
50
+ ):
51
+ raise ValueError(
52
+ "base_url must be an absolute HTTP(S) URL without credentials or query"
53
+ )
54
+ if not all((client_id, client_secret, tenant, store)):
55
+ raise ValueError("client_id, client_secret, tenant, and store must be nonempty")
56
+ if isinstance(scopes, str):
57
+ raise TypeError("scopes must be a sequence of scope strings")
58
+ self.tenant = tenant
59
+ self.store = store
60
+ base_url = str(url).rstrip("/")
61
+ auth = ClientCredentialsAuth(base_url + "/oauth/token", client_id, client_secret, scopes)
62
+ self.inner = GeneratedClient(
63
+ base_url=base_url,
64
+ timeout=timeout if isinstance(timeout, httpx.Timeout) else httpx.Timeout(timeout),
65
+ verify_ssl=verify,
66
+ follow_redirects=False,
67
+ headers={"Accept": "application/fhir+json", "User-Agent": "phenostore-sdk-python"},
68
+ httpx_args={"auth": auth, "transport": transport},
69
+ raise_on_unexpected_status=True,
70
+ )
71
+
72
+ def _read(self, resource_type: str, id: str) -> JSON:
73
+ return read_resource._get_kwargs(
74
+ self.tenant, self.store, cast(ResourceType, resource_type), id
75
+ )
76
+
77
+ def _create(
78
+ self, resource_type: str, body: Mapping[str, Any], if_none_exist: str | None
79
+ ) -> JSON:
80
+ return create_resource._get_kwargs(
81
+ self.tenant,
82
+ self.store,
83
+ cast(ResourceType, resource_type),
84
+ body=FhirResource.from_dict(body),
85
+ if_none_exist=if_none_exist if if_none_exist is not None else UNSET,
86
+ )
87
+
88
+ def _update(
89
+ self, resource_type: str, id: str, body: Mapping[str, Any], if_match: str | None
90
+ ) -> JSON:
91
+ return update_resource._get_kwargs(
92
+ self.tenant,
93
+ self.store,
94
+ cast(ResourceType, resource_type),
95
+ id,
96
+ body=FhirResource.from_dict(body),
97
+ if_match=if_match if if_match is not None else UNSET,
98
+ )
99
+
100
+ def _delete(self, resource_type: str, id: str) -> JSON:
101
+ return delete_resource._get_kwargs(
102
+ self.tenant, self.store, cast(ResourceType, resource_type), id
103
+ )
104
+
105
+ def _search(self, resource_type: str, params: SearchParams | None) -> JSON:
106
+ kwargs = search_resources._get_kwargs(
107
+ self.tenant, self.store, cast(ResourceType, resource_type)
108
+ )
109
+ # The generated signature only includes parameters explicitly named in the
110
+ # spec. FHIR also allows arbitrary names, modifiers, chains and repetition.
111
+ kwargs["params"] = params if params is not None else {}
112
+ return kwargs
113
+
114
+ def _bundle(self, bundle: Mapping[str, Any]) -> JSON:
115
+ return process_bundle._get_kwargs(
116
+ self.tenant, self.store, body=FhirResource.from_dict(bundle)
117
+ )
118
+
119
+
120
+ class Client(_BaseClient):
121
+ """Synchronous Phenostore client with lazy OAuth2 authentication.
122
+
123
+ Use as a context manager or call close() to release pooled connections.
124
+ The transport, timeout and TLS configuration also apply to token requests.
125
+ Access inner to use generated API functions and typed models directly.
126
+ """
127
+
128
+ def __init__(
129
+ self,
130
+ base_url: str,
131
+ client_id: str,
132
+ client_secret: str,
133
+ tenant: str,
134
+ store: str,
135
+ *,
136
+ scopes: Sequence[str] = (),
137
+ timeout: float | httpx.Timeout = 30.0,
138
+ verify: bool | ssl.SSLContext = True,
139
+ transport: httpx.BaseTransport | None = None,
140
+ ) -> None:
141
+ super().__init__(
142
+ base_url,
143
+ client_id,
144
+ client_secret,
145
+ tenant,
146
+ store,
147
+ scopes=scopes,
148
+ timeout=timeout,
149
+ verify=verify,
150
+ transport=transport,
151
+ )
152
+ # Construct once before the client can be shared between threads. Token
153
+ # acquisition stays lazy; the generated client's lazy initializer is not
154
+ # synchronized and can otherwise allocate multiple connection pools.
155
+ self.inner.get_httpx_client()
156
+
157
+ def _request(self, kwargs: JSON) -> httpx.Response:
158
+ response = self.inner.get_httpx_client().request(**kwargs)
159
+ if not 200 <= response.status_code < 300:
160
+ raise OperationOutcomeError(response)
161
+ return response
162
+
163
+ def read_resource(self, resource_type: str, id: str) -> JSON:
164
+ """Read a resource, preserving every JSON field."""
165
+ return _json_object(self._request(self._read(resource_type, id)))
166
+
167
+ def create_resource(
168
+ self, resource_type: str, body: Mapping[str, Any], *, if_none_exist: str | None = None
169
+ ) -> JSON:
170
+ """Create a resource, optionally with conditional search criteria."""
171
+ return _json_object(self._request(self._create(resource_type, body, if_none_exist)))
172
+
173
+ def update_resource(
174
+ self, resource_type: str, id: str, body: Mapping[str, Any], *, if_match: str | None = None
175
+ ) -> JSON:
176
+ """Update or upsert, optionally requiring an ETag such as W/\"1\"."""
177
+ return _json_object(self._request(self._update(resource_type, id, body, if_match)))
178
+
179
+ def delete_resource(self, resource_type: str, id: str) -> None:
180
+ """Delete a resource. Deleted and missing resources may raise API errors."""
181
+ self._request(self._delete(resource_type, id))
182
+
183
+ def search_resources(self, resource_type: str, params: SearchParams | None = None) -> JSON:
184
+ """Search with arbitrary FHIR parameters; sequence values repeat a parameter."""
185
+ return _json_object(self._request(self._search(resource_type, params)))
186
+
187
+ def process_bundle(self, bundle: Mapping[str, Any]) -> JSON:
188
+ """Execute a transaction or batch bundle."""
189
+ return _json_object(self._request(self._bundle(bundle)))
190
+
191
+ def close(self) -> None:
192
+ """Close the underlying synchronous HTTP client."""
193
+ self.inner.get_httpx_client().close()
194
+
195
+ def __enter__(self) -> Self:
196
+ self.inner.get_httpx_client().__enter__()
197
+ return self
198
+
199
+ def __exit__(self, *args: Any) -> None:
200
+ self.close()
201
+
202
+
203
+ class AsyncClient(_BaseClient):
204
+ """Asynchronous equivalent of Client. Use async with or await aclose()."""
205
+
206
+ def __init__(
207
+ self,
208
+ base_url: str,
209
+ client_id: str,
210
+ client_secret: str,
211
+ tenant: str,
212
+ store: str,
213
+ *,
214
+ scopes: Sequence[str] = (),
215
+ timeout: float | httpx.Timeout = 30.0,
216
+ verify: bool | ssl.SSLContext = True,
217
+ transport: httpx.AsyncBaseTransport | None = None,
218
+ ) -> None:
219
+ super().__init__(
220
+ base_url,
221
+ client_id,
222
+ client_secret,
223
+ tenant,
224
+ store,
225
+ scopes=scopes,
226
+ timeout=timeout,
227
+ verify=verify,
228
+ transport=transport,
229
+ )
230
+
231
+ async def _request(self, kwargs: JSON) -> httpx.Response:
232
+ response = await self.inner.get_async_httpx_client().request(**kwargs)
233
+ if not 200 <= response.status_code < 300:
234
+ raise OperationOutcomeError(response)
235
+ return response
236
+
237
+ async def read_resource(self, resource_type: str, id: str) -> JSON:
238
+ """Read a resource, preserving every JSON field."""
239
+ return _json_object(await self._request(self._read(resource_type, id)))
240
+
241
+ async def create_resource(
242
+ self, resource_type: str, body: Mapping[str, Any], *, if_none_exist: str | None = None
243
+ ) -> JSON:
244
+ """Create a resource, optionally with conditional search criteria."""
245
+ return _json_object(await self._request(self._create(resource_type, body, if_none_exist)))
246
+
247
+ async def update_resource(
248
+ self, resource_type: str, id: str, body: Mapping[str, Any], *, if_match: str | None = None
249
+ ) -> JSON:
250
+ """Update or upsert, optionally requiring an ETag."""
251
+ return _json_object(await self._request(self._update(resource_type, id, body, if_match)))
252
+
253
+ async def delete_resource(self, resource_type: str, id: str) -> None:
254
+ """Delete a resource."""
255
+ await self._request(self._delete(resource_type, id))
256
+
257
+ async def search_resources(
258
+ self, resource_type: str, params: SearchParams | None = None
259
+ ) -> JSON:
260
+ """Search with arbitrary FHIR parameters, including repeated values."""
261
+ return _json_object(await self._request(self._search(resource_type, params)))
262
+
263
+ async def process_bundle(self, bundle: Mapping[str, Any]) -> JSON:
264
+ """Execute a transaction or batch bundle."""
265
+ return _json_object(await self._request(self._bundle(bundle)))
266
+
267
+ async def aclose(self) -> None:
268
+ """Close the underlying asynchronous HTTP client."""
269
+ await self.inner.get_async_httpx_client().aclose()
270
+
271
+ async def __aenter__(self) -> Self:
272
+ await self.inner.get_async_httpx_client().__aenter__()
273
+ return self
274
+
275
+ async def __aexit__(self, *args: Any) -> None:
276
+ await self.aclose()
phenostore/errors.py ADDED
@@ -0,0 +1,93 @@
1
+ """Errors raised by the convenience clients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+
11
+ class OperationOutcomeError(Exception):
12
+ """An API error, retaining the HTTP response and unmodified response bytes."""
13
+
14
+ def __init__(self, response: httpx.Response) -> None:
15
+ self.response = response
16
+ self.status_code = response.status_code
17
+ self.body = response.content
18
+ self.outcome: dict[str, Any] | None = None
19
+ message = f"phenostore: HTTP {self.status_code}"
20
+ try:
21
+ parsed = response.json()
22
+ except (ValueError, UnicodeError):
23
+ parsed = None
24
+ if isinstance(parsed, dict):
25
+ self.outcome = parsed
26
+ issues = parsed.get("issue")
27
+ if isinstance(issues, list) and issues and isinstance(issues[0], dict):
28
+ issue = issues[0]
29
+ message = "[{}/{}] {}".format(
30
+ issue.get("severity", ""), issue.get("code", ""), issue.get("diagnostics", "")
31
+ )
32
+ super().__init__(message)
33
+
34
+
35
+ class AuthenticationError(Exception):
36
+ """Token exchange failed or returned an invalid token response."""
37
+
38
+ def __init__(self, response: httpx.Response, message: str = "token exchange failed") -> None:
39
+ self.response = response
40
+ self.status_code = response.status_code
41
+ self.body = response.content
42
+ # Avoid putting credentials or token response bodies in exception messages.
43
+ super().__init__(f"phenostore: {message} (HTTP {self.status_code})")
44
+
45
+
46
+ class InvalidResponseError(ValueError):
47
+ """A successful API response did not contain a JSON object."""
48
+
49
+ def __init__(self, response: httpx.Response) -> None:
50
+ self.response = response
51
+ super().__init__(f"phenostore: expected a JSON object (HTTP {response.status_code})")
52
+
53
+
54
+ def _has_status(error: BaseException | None, status: int) -> bool:
55
+ seen: set[int] = set()
56
+ while error is not None and id(error) not in seen:
57
+ seen.add(id(error))
58
+ if isinstance(error, OperationOutcomeError):
59
+ return error.status_code == status
60
+ error = error.__cause__ or error.__context__
61
+ return False
62
+
63
+
64
+ def is_not_found(error: BaseException | None) -> bool:
65
+ """Return whether an error (including chained exceptions) represents HTTP 404."""
66
+ return _has_status(error, 404)
67
+
68
+
69
+ def is_gone(error: BaseException | None) -> bool:
70
+ """Return whether an error represents HTTP 410."""
71
+ return _has_status(error, 410)
72
+
73
+
74
+ def is_conflict(error: BaseException | None) -> bool:
75
+ """Return whether an error represents HTTP 412 (a failed precondition)."""
76
+ return _has_status(error, 412)
77
+
78
+
79
+ def is_forbidden(error: BaseException | None) -> bool:
80
+ """Return whether an error represents HTTP 403."""
81
+ return _has_status(error, 403)
82
+
83
+
84
+ def _json_object(response: httpx.Response) -> dict[str, Any]:
85
+ if not 200 <= response.status_code < 300:
86
+ raise OperationOutcomeError(response)
87
+ try:
88
+ result = response.json()
89
+ except (json.JSONDecodeError, UnicodeError) as exc:
90
+ raise InvalidResponseError(response) from exc
91
+ if not isinstance(result, dict):
92
+ raise InvalidResponseError(response)
93
+ return result
@@ -0,0 +1,8 @@
1
+ """A client library for accessing Phenostore FHIR Data API"""
2
+
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """Contains methods for accessing the API"""
@@ -0,0 +1 @@
1
+ """Contains endpoint functions for accessing the API"""
@@ -0,0 +1,194 @@
1
+ from http import HTTPStatus
2
+ from typing import Any
3
+ from urllib.parse import quote
4
+
5
+ import httpx
6
+
7
+ from ... import errors
8
+ from ...client import AuthenticatedClient, Client
9
+ from ...models.bulk_operation_status import BulkOperationStatus
10
+ from ...models.operation_outcome import OperationOutcome
11
+ from ...types import Response
12
+
13
+
14
+ def _get_kwargs(
15
+ tenant_id: str,
16
+ store_id: str,
17
+ op_id: str,
18
+ ) -> dict[str, Any]:
19
+
20
+ _kwargs: dict[str, Any] = {
21
+ "method": "get",
22
+ "url": "/v1/tenants/{tenant_id}/stores/{store_id}/$bulk/{op_id}".format(
23
+ tenant_id=quote(str(tenant_id), safe=""),
24
+ store_id=quote(str(store_id), safe=""),
25
+ op_id=quote(str(op_id), safe=""),
26
+ ),
27
+ }
28
+
29
+ return _kwargs
30
+
31
+
32
+ def _parse_response(
33
+ *, client: AuthenticatedClient | Client, response: httpx.Response
34
+ ) -> BulkOperationStatus | OperationOutcome | None:
35
+ if response.status_code == 200:
36
+ response_200 = BulkOperationStatus.from_dict(response.json())
37
+
38
+ return response_200
39
+
40
+ if response.status_code == 202:
41
+ response_202 = BulkOperationStatus.from_dict(response.json())
42
+
43
+ return response_202
44
+
45
+ if response.status_code == 404:
46
+ response_404 = OperationOutcome.from_dict(response.json())
47
+
48
+ return response_404
49
+
50
+ if client.raise_on_unexpected_status:
51
+ raise errors.UnexpectedStatus(response.status_code, response.content)
52
+ else:
53
+ return None
54
+
55
+
56
+ def _build_response(
57
+ *, client: AuthenticatedClient | Client, response: httpx.Response
58
+ ) -> Response[BulkOperationStatus | OperationOutcome]:
59
+ return Response(
60
+ status_code=HTTPStatus(response.status_code),
61
+ content=response.content,
62
+ headers=response.headers,
63
+ parsed=_parse_response(client=client, response=response),
64
+ )
65
+
66
+
67
+ def sync_detailed(
68
+ tenant_id: str,
69
+ store_id: str,
70
+ op_id: str,
71
+ *,
72
+ client: AuthenticatedClient | Client,
73
+ ) -> Response[BulkOperationStatus | OperationOutcome]:
74
+ """Get bulk operation status
75
+
76
+ Args:
77
+ tenant_id (str):
78
+ store_id (str):
79
+ op_id (str):
80
+
81
+ Raises:
82
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
83
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
84
+
85
+ Returns:
86
+ Response[BulkOperationStatus | OperationOutcome]
87
+ """
88
+
89
+ kwargs = _get_kwargs(
90
+ tenant_id=tenant_id,
91
+ store_id=store_id,
92
+ op_id=op_id,
93
+ )
94
+
95
+ response = client.get_httpx_client().request(
96
+ **kwargs,
97
+ )
98
+
99
+ return _build_response(client=client, response=response)
100
+
101
+
102
+ def sync(
103
+ tenant_id: str,
104
+ store_id: str,
105
+ op_id: str,
106
+ *,
107
+ client: AuthenticatedClient | Client,
108
+ ) -> BulkOperationStatus | OperationOutcome | None:
109
+ """Get bulk operation status
110
+
111
+ Args:
112
+ tenant_id (str):
113
+ store_id (str):
114
+ op_id (str):
115
+
116
+ Raises:
117
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
118
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
119
+
120
+ Returns:
121
+ BulkOperationStatus | OperationOutcome
122
+ """
123
+
124
+ return sync_detailed(
125
+ tenant_id=tenant_id,
126
+ store_id=store_id,
127
+ op_id=op_id,
128
+ client=client,
129
+ ).parsed
130
+
131
+
132
+ async def asyncio_detailed(
133
+ tenant_id: str,
134
+ store_id: str,
135
+ op_id: str,
136
+ *,
137
+ client: AuthenticatedClient | Client,
138
+ ) -> Response[BulkOperationStatus | OperationOutcome]:
139
+ """Get bulk operation status
140
+
141
+ Args:
142
+ tenant_id (str):
143
+ store_id (str):
144
+ op_id (str):
145
+
146
+ Raises:
147
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
148
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
149
+
150
+ Returns:
151
+ Response[BulkOperationStatus | OperationOutcome]
152
+ """
153
+
154
+ kwargs = _get_kwargs(
155
+ tenant_id=tenant_id,
156
+ store_id=store_id,
157
+ op_id=op_id,
158
+ )
159
+
160
+ response = await client.get_async_httpx_client().request(**kwargs)
161
+
162
+ return _build_response(client=client, response=response)
163
+
164
+
165
+ async def asyncio(
166
+ tenant_id: str,
167
+ store_id: str,
168
+ op_id: str,
169
+ *,
170
+ client: AuthenticatedClient | Client,
171
+ ) -> BulkOperationStatus | OperationOutcome | None:
172
+ """Get bulk operation status
173
+
174
+ Args:
175
+ tenant_id (str):
176
+ store_id (str):
177
+ op_id (str):
178
+
179
+ Raises:
180
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
181
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
182
+
183
+ Returns:
184
+ BulkOperationStatus | OperationOutcome
185
+ """
186
+
187
+ return (
188
+ await asyncio_detailed(
189
+ tenant_id=tenant_id,
190
+ store_id=store_id,
191
+ op_id=op_id,
192
+ client=client,
193
+ )
194
+ ).parsed