anaplan-sdk 0.5.0a1__py3-none-any.whl → 0.5.0a2__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.
- anaplan_sdk/_async_clients/_alm.py +24 -31
- anaplan_sdk/_async_clients/_audit.py +7 -9
- anaplan_sdk/_async_clients/_bulk.py +66 -64
- anaplan_sdk/_async_clients/_cloud_works.py +36 -34
- anaplan_sdk/_async_clients/_cw_flow.py +13 -13
- anaplan_sdk/_async_clients/_transactional.py +40 -33
- anaplan_sdk/_clients/_alm.py +22 -31
- anaplan_sdk/_clients/_audit.py +7 -10
- anaplan_sdk/_clients/_bulk.py +63 -62
- anaplan_sdk/_clients/_cloud_works.py +32 -32
- anaplan_sdk/_clients/_cw_flow.py +13 -13
- anaplan_sdk/_clients/_transactional.py +42 -33
- anaplan_sdk/{_base.py → _services.py} +86 -72
- anaplan_sdk/models/__init__.py +2 -0
- anaplan_sdk/models/_bulk.py +2 -9
- {anaplan_sdk-0.5.0a1.dist-info → anaplan_sdk-0.5.0a2.dist-info}/METADATA +1 -1
- anaplan_sdk-0.5.0a2.dist-info/RECORD +30 -0
- anaplan_sdk-0.5.0a1.dist-info/RECORD +0 -30
- {anaplan_sdk-0.5.0a1.dist-info → anaplan_sdk-0.5.0a2.dist-info}/WHEEL +0 -0
- {anaplan_sdk-0.5.0a1.dist-info → anaplan_sdk-0.5.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,7 @@
|
|
1
1
|
import logging
|
2
|
-
from asyncio import sleep
|
3
2
|
from typing import Literal, overload
|
4
3
|
|
5
|
-
import
|
6
|
-
|
7
|
-
from anaplan_sdk._base import _AsyncBaseClient
|
4
|
+
from anaplan_sdk._services import _AsyncHttpService
|
8
5
|
from anaplan_sdk.exceptions import AnaplanActionError
|
9
6
|
from anaplan_sdk.models import (
|
10
7
|
ModelRevision,
|
@@ -18,14 +15,11 @@ from anaplan_sdk.models import (
|
|
18
15
|
logger = logging.getLogger("anaplan_sdk")
|
19
16
|
|
20
17
|
|
21
|
-
class _AsyncAlmClient
|
22
|
-
def __init__(
|
23
|
-
self
|
24
|
-
) -> None:
|
25
|
-
self.status_poll_delay = status_poll_delay
|
18
|
+
class _AsyncAlmClient:
|
19
|
+
def __init__(self, http: _AsyncHttpService, model_id: str) -> None:
|
20
|
+
self._http = http
|
26
21
|
self._model_id = model_id
|
27
22
|
self._url = f"https://api.anaplan.com/2/0/models/{model_id}"
|
28
|
-
super().__init__(retry_count, client)
|
29
23
|
|
30
24
|
async def change_model_status(self, status: Literal["online", "offline"]) -> None:
|
31
25
|
"""
|
@@ -33,14 +27,14 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
33
27
|
:param status: The status of the model. Can be either "online" or "offline".
|
34
28
|
"""
|
35
29
|
logger.info(f"Changed model status to '{status}' for model {self._model_id}.")
|
36
|
-
await self.
|
30
|
+
await self._http.put(f"{self._url}/onlineStatus", json={"status": status})
|
37
31
|
|
38
32
|
async def get_revisions(self) -> list[Revision]:
|
39
33
|
"""
|
40
34
|
Use this call to return a list of revisions for a specific model.
|
41
35
|
:return: A list of revisions for a specific model.
|
42
36
|
"""
|
43
|
-
res = await self.
|
37
|
+
res = await self._http.get(f"{self._url}/alm/revisions")
|
44
38
|
return [Revision.model_validate(e) for e in res.get("revisions", [])]
|
45
39
|
|
46
40
|
async def get_latest_revision(self) -> Revision | None:
|
@@ -52,7 +46,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
52
46
|
latest revision.
|
53
47
|
:return: The latest revision for a specific model, or None if no revisions exist.
|
54
48
|
"""
|
55
|
-
res = (await self.
|
49
|
+
res = (await self._http.get(f"{self._url}/alm/latestRevision")).get("revisions")
|
56
50
|
return Revision.model_validate(res[0]) if res else None
|
57
51
|
|
58
52
|
async def get_syncable_revisions(self, source_model_id: str) -> list[Revision]:
|
@@ -65,7 +59,9 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
65
59
|
:param source_model_id: The ID of the source model.
|
66
60
|
:return: A list of revisions that can be synchronized to the target model.
|
67
61
|
"""
|
68
|
-
res = await self.
|
62
|
+
res = await self._http.get(
|
63
|
+
f"{self._url}/alm/syncableRevisions?sourceModelId={source_model_id}"
|
64
|
+
)
|
69
65
|
return [Revision.model_validate(e) for e in res.get("revisions", [])]
|
70
66
|
|
71
67
|
async def create_revision(self, name: str, description: str) -> Revision:
|
@@ -75,7 +71,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
75
71
|
:param description: The description of the revision.
|
76
72
|
:return: The created Revision Info.
|
77
73
|
"""
|
78
|
-
res = await self.
|
74
|
+
res = await self._http.post(
|
79
75
|
f"{self._url}/alm/revisions", json={"name": name, "description": description}
|
80
76
|
)
|
81
77
|
rev = Revision.model_validate(res["revision"])
|
@@ -88,7 +84,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
88
84
|
they completed within the last 48 hours.
|
89
85
|
:return: A list of sync tasks in descending order of creation time.
|
90
86
|
"""
|
91
|
-
res = await self.
|
87
|
+
res = await self._http.get(f"{self._url}/alm/syncTasks")
|
92
88
|
return [TaskSummary.model_validate(e) for e in res.get("tasks", [])]
|
93
89
|
|
94
90
|
async def get_sync_task(self, task_id: str) -> SyncTask:
|
@@ -97,7 +93,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
97
93
|
:param task_id: The ID of the sync task.
|
98
94
|
:return: The sync task information.
|
99
95
|
"""
|
100
|
-
res = await self.
|
96
|
+
res = await self._http.get(f"{self._url}/alm/syncTasks/{task_id}")
|
101
97
|
return SyncTask.model_validate(res["task"])
|
102
98
|
|
103
99
|
async def sync_models(
|
@@ -123,7 +119,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
123
119
|
"sourceModelId": source_model_id,
|
124
120
|
"targetRevisionId": target_revision_id,
|
125
121
|
}
|
126
|
-
res = await self.
|
122
|
+
res = await self._http.post(f"{self._url}/alm/syncTasks", json=payload)
|
127
123
|
task = await self.get_sync_task(res["task"]["taskId"])
|
128
124
|
logger.info(
|
129
125
|
f"Started sync task '{task.id}' from Model '{source_model_id}' "
|
@@ -131,8 +127,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
131
127
|
)
|
132
128
|
if not wait_for_completion:
|
133
129
|
return task
|
134
|
-
|
135
|
-
await sleep(self.status_poll_delay)
|
130
|
+
task = await self._http.poll_task(self.get_sync_task, task.id)
|
136
131
|
if not task.result.successful:
|
137
132
|
msg = f"Sync task {task.id} completed with errors: {task.result.error}."
|
138
133
|
logger.error(msg)
|
@@ -147,7 +142,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
147
142
|
:param revision_id: The ID of the revision.
|
148
143
|
:return: A list of models that had a specific revision applied to them.
|
149
144
|
"""
|
150
|
-
res = await self.
|
145
|
+
res = await self._http.get(f"{self._url}/alm/revisions/{revision_id}/appliedToModels")
|
151
146
|
return [ModelRevision.model_validate(e) for e in res.get("appliedToModels", [])]
|
152
147
|
|
153
148
|
async def create_comparison_report(
|
@@ -172,7 +167,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
172
167
|
"sourceModelId": source_model_id,
|
173
168
|
"targetRevisionId": target_revision_id,
|
174
169
|
}
|
175
|
-
res = await self.
|
170
|
+
res = await self._http.post(f"{self._url}/alm/comparisonReportTasks", json=payload)
|
176
171
|
task = await self.get_comparison_report_task(res["task"]["taskId"])
|
177
172
|
logger.info(
|
178
173
|
f"Started Comparison Report task '{task.id}' between Model '{source_model_id}' "
|
@@ -180,8 +175,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
180
175
|
)
|
181
176
|
if not wait_for_completion:
|
182
177
|
return task
|
183
|
-
|
184
|
-
await sleep(self.status_poll_delay)
|
178
|
+
task = await self._http.poll_task(self.get_comparison_report_task, task.id)
|
185
179
|
if not task.result.successful:
|
186
180
|
msg = f"Comparison Report task {task.id} completed with errors: {task.result.error}."
|
187
181
|
logger.error(msg)
|
@@ -195,7 +189,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
195
189
|
:param task_id: The ID of the comparison report task.
|
196
190
|
:return: The report task information.
|
197
191
|
"""
|
198
|
-
res = await self.
|
192
|
+
res = await self._http.get(f"{self._url}/alm/comparisonReportTasks/{task_id}")
|
199
193
|
return ReportTask.model_validate(res["task"])
|
200
194
|
|
201
195
|
async def get_comparison_report(self, task: ReportTask) -> bytes:
|
@@ -204,7 +198,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
204
198
|
:param task: The report task object containing the task ID.
|
205
199
|
:return: The binary content of the comparison report.
|
206
200
|
"""
|
207
|
-
return await self.
|
201
|
+
return await self._http.get_binary(
|
208
202
|
f"{self._url}/alm/comparisonReports/"
|
209
203
|
f"{task.result.target_revision_id}/{task.result.source_revision_id}"
|
210
204
|
)
|
@@ -248,7 +242,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
248
242
|
"sourceModelId": source_model_id,
|
249
243
|
"targetRevisionId": target_revision_id,
|
250
244
|
}
|
251
|
-
res = await self.
|
245
|
+
res = await self._http.post(f"{self._url}/alm/summaryReportTasks", json=payload)
|
252
246
|
task = await self.get_comparison_summary_task(res["task"]["taskId"])
|
253
247
|
logger.info(
|
254
248
|
f"Started Comparison Summary task '{task.id}' between Model '{source_model_id}' "
|
@@ -256,8 +250,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
256
250
|
)
|
257
251
|
if not wait_for_completion:
|
258
252
|
return task
|
259
|
-
|
260
|
-
await sleep(self.status_poll_delay)
|
253
|
+
task = await self._http.poll_task(self.get_comparison_summary_task, task.id)
|
261
254
|
if not task.result.successful:
|
262
255
|
msg = f"Comparison Summary task {task.id} completed with errors: {task.result.error}."
|
263
256
|
logger.error(msg)
|
@@ -271,7 +264,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
271
264
|
:param task_id: The ID of the comparison summary task.
|
272
265
|
:return: The report task information.
|
273
266
|
"""
|
274
|
-
res = await self.
|
267
|
+
res = await self._http.get(f"{self._url}/alm/summaryReportTasks/{task_id}")
|
275
268
|
return ReportTask.model_validate(res["task"])
|
276
269
|
|
277
270
|
async def get_comparison_summary(self, task: ReportTask) -> SummaryReport:
|
@@ -280,7 +273,7 @@ class _AsyncAlmClient(_AsyncBaseClient):
|
|
280
273
|
:param task: The summary task object containing the task ID.
|
281
274
|
:return: The binary content of the comparison summary.
|
282
275
|
"""
|
283
|
-
res = await self.
|
276
|
+
res = await self._http.get(
|
284
277
|
f"{self._url}/alm/summaryReports/"
|
285
278
|
f"{task.result.target_revision_id}/{task.result.source_revision_id}"
|
286
279
|
)
|
@@ -1,18 +1,16 @@
|
|
1
1
|
from typing import Any, Literal
|
2
2
|
|
3
|
-
import
|
4
|
-
|
5
|
-
from anaplan_sdk._base import _AsyncBaseClient
|
3
|
+
from anaplan_sdk._services import _AsyncHttpService
|
6
4
|
from anaplan_sdk.models import User
|
7
5
|
|
8
6
|
Event = Literal["all", "byok", "user_activity"]
|
9
7
|
|
10
8
|
|
11
|
-
class _AsyncAuditClient
|
12
|
-
def __init__(self,
|
9
|
+
class _AsyncAuditClient:
|
10
|
+
def __init__(self, http: _AsyncHttpService) -> None:
|
11
|
+
self._http = http
|
13
12
|
self._limit = 10_000
|
14
13
|
self._url = "https://audit.anaplan.com/audit/api/1/events"
|
15
|
-
super().__init__(retry_count, client)
|
16
14
|
|
17
15
|
async def get_users(self, search_pattern: str | None = None) -> list[User]:
|
18
16
|
"""
|
@@ -26,7 +24,7 @@ class _AsyncAuditClient(_AsyncBaseClient):
|
|
26
24
|
params = {"s": search_pattern} if search_pattern else None
|
27
25
|
return [
|
28
26
|
User.model_validate(e)
|
29
|
-
for e in await self.
|
27
|
+
for e in await self._http.get_paginated(
|
30
28
|
"https://api.anaplan.com/2/0/users", "users", params=params
|
31
29
|
)
|
32
30
|
]
|
@@ -37,7 +35,7 @@ class _AsyncAuditClient(_AsyncBaseClient):
|
|
37
35
|
:return: The requested or currently authenticated User.
|
38
36
|
"""
|
39
37
|
return User.model_validate(
|
40
|
-
(await self.
|
38
|
+
(await self._http.get(f"https://api.anaplan.com/2/0/users/{user_id}")).get("user")
|
41
39
|
)
|
42
40
|
|
43
41
|
async def get_events(
|
@@ -51,7 +49,7 @@ class _AsyncAuditClient(_AsyncBaseClient):
|
|
51
49
|
:return: A list of log entries, each containing a dictionary with event details.
|
52
50
|
"""
|
53
51
|
return list(
|
54
|
-
await self.
|
52
|
+
await self._http.get_paginated(
|
55
53
|
self._url,
|
56
54
|
"response",
|
57
55
|
params={"type": event_type, "intervalInHours": days_into_past * 24},
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import logging
|
2
|
-
from asyncio import gather
|
2
|
+
from asyncio import gather
|
3
3
|
from copy import copy
|
4
4
|
from typing import AsyncIterator, Iterator
|
5
5
|
|
@@ -7,7 +7,7 @@ import httpx
|
|
7
7
|
from typing_extensions import Self
|
8
8
|
|
9
9
|
from anaplan_sdk._auth import _create_auth
|
10
|
-
from anaplan_sdk.
|
10
|
+
from anaplan_sdk._services import _AsyncHttpService, action_url
|
11
11
|
from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierException
|
12
12
|
from anaplan_sdk.models import (
|
13
13
|
Action,
|
@@ -30,7 +30,7 @@ from ._transactional import _AsyncTransactionalClient
|
|
30
30
|
logger = logging.getLogger("anaplan_sdk")
|
31
31
|
|
32
32
|
|
33
|
-
class AsyncClient
|
33
|
+
class AsyncClient:
|
34
34
|
"""
|
35
35
|
Asynchronous Anaplan Client. For guides and examples
|
36
36
|
refer to https://vinzenzklass.github.io/anaplan-sdk.
|
@@ -49,9 +49,11 @@ class AsyncClient(_AsyncBaseClient):
|
|
49
49
|
auth: httpx.Auth | None = None,
|
50
50
|
timeout: float | httpx.Timeout = 30,
|
51
51
|
retry_count: int = 2,
|
52
|
+
page_size: int = 5_000,
|
52
53
|
status_poll_delay: int = 1,
|
53
54
|
upload_chunk_size: int = 25_000_000,
|
54
55
|
allow_file_creation: bool = False,
|
56
|
+
**httpx_kwargs,
|
55
57
|
) -> None:
|
56
58
|
"""
|
57
59
|
Asynchronous Anaplan Client. For guides and examples
|
@@ -84,6 +86,9 @@ class AsyncClient(_AsyncBaseClient):
|
|
84
86
|
:param retry_count: The number of times to retry an HTTP request if it fails. Set this to 0
|
85
87
|
to never retry. Defaults to 2, meaning each HTTP Operation will be tried a total
|
86
88
|
number of 2 times.
|
89
|
+
:param page_size: The number of items to return per page when paginating through results.
|
90
|
+
Defaults to 5000. This is the maximum number of items that can be returned per
|
91
|
+
request. If you pass a value greater than 5000, it will be capped to 5000.
|
87
92
|
:param status_poll_delay: The delay between polling the status of a task.
|
88
93
|
:param upload_chunk_size: The size of the chunks to upload. This is the maximum size of
|
89
94
|
each chunk. Defaults to 25MB.
|
@@ -92,39 +97,31 @@ class AsyncClient(_AsyncBaseClient):
|
|
92
97
|
altogether. A file that is created this way will not be referenced by any action in
|
93
98
|
anaplan until manually assigned so there is typically no value in dynamically
|
94
99
|
creating new files and uploading content to them.
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
)
|
107
|
-
),
|
108
|
-
timeout=timeout,
|
100
|
+
:param httpx_kwargs: Additional keyword arguments to pass to the `httpx.AsyncClient`.
|
101
|
+
This can be used to set additional options such as proxies, headers, etc. See
|
102
|
+
https://www.python-httpx.org/api/#asyncclient for the full list of arguments.
|
103
|
+
"""
|
104
|
+
_auth = auth or _create_auth(
|
105
|
+
token=token,
|
106
|
+
user_email=user_email,
|
107
|
+
password=password,
|
108
|
+
certificate=certificate,
|
109
|
+
private_key=private_key,
|
110
|
+
private_key_password=private_key_password,
|
109
111
|
)
|
112
|
+
_client = httpx.AsyncClient(auth=_auth, timeout=timeout, **httpx_kwargs)
|
113
|
+
self._http = _AsyncHttpService(_client, retry_count, page_size, status_poll_delay)
|
110
114
|
self._workspace_id = workspace_id
|
111
115
|
self._model_id = model_id
|
112
|
-
self._retry_count = retry_count
|
113
116
|
self._url = f"https://api.anaplan.com/2/0/workspaces/{workspace_id}/models/{model_id}"
|
114
117
|
self._transactional_client = (
|
115
|
-
_AsyncTransactionalClient(
|
116
|
-
)
|
117
|
-
self._alm_client = (
|
118
|
-
_AsyncAlmClient(_client, model_id, self._retry_count, status_poll_delay)
|
119
|
-
if model_id
|
120
|
-
else None
|
118
|
+
_AsyncTransactionalClient(self._http, model_id) if model_id else None
|
121
119
|
)
|
122
|
-
self.
|
123
|
-
self.
|
124
|
-
self.
|
120
|
+
self._alm_client = _AsyncAlmClient(self._http, model_id) if model_id else None
|
121
|
+
self._audit = _AsyncAuditClient(self._http)
|
122
|
+
self._cloud_works = _AsyncCloudWorksClient(self._http)
|
125
123
|
self.upload_chunk_size = upload_chunk_size
|
126
124
|
self.allow_file_creation = allow_file_creation
|
127
|
-
super().__init__(retry_count, _client)
|
128
125
|
|
129
126
|
@classmethod
|
130
127
|
def from_existing(
|
@@ -152,12 +149,8 @@ class AsyncClient(_AsyncBaseClient):
|
|
152
149
|
f"with workspace_id={new_ws_id}, model_id={new_model_id}."
|
153
150
|
)
|
154
151
|
client._url = f"https://api.anaplan.com/2/0/workspaces/{new_ws_id}/models/{new_model_id}"
|
155
|
-
client._transactional_client = _AsyncTransactionalClient(
|
156
|
-
|
157
|
-
)
|
158
|
-
client._alm_client = _AsyncAlmClient(
|
159
|
-
existing._client, new_model_id, existing._retry_count, existing.status_poll_delay
|
160
|
-
)
|
152
|
+
client._transactional_client = _AsyncTransactionalClient(existing._http, new_model_id)
|
153
|
+
client._alm_client = _AsyncAlmClient(existing._http, new_model_id)
|
161
154
|
return client
|
162
155
|
|
163
156
|
@property
|
@@ -177,14 +170,14 @@ class AsyncClient(_AsyncBaseClient):
|
|
177
170
|
return self._cloud_works
|
178
171
|
|
179
172
|
@property
|
180
|
-
def
|
173
|
+
def tr(self) -> _AsyncTransactionalClient:
|
181
174
|
"""
|
182
175
|
The Transactional Client provides access to the Anaplan Transactional API. This is useful
|
183
176
|
for more advanced use cases where you need to interact with the Anaplan Model in a more
|
184
177
|
granular way.
|
185
178
|
|
186
179
|
If you instantiated the client without the field `model_id`, this will raise a
|
187
|
-
|
180
|
+
`ValueError`, since none of the endpoints can be invoked without the model Id.
|
188
181
|
:return: The Transactional Client.
|
189
182
|
"""
|
190
183
|
if not self._transactional_client:
|
@@ -229,7 +222,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
229
222
|
params["s"] = search_pattern
|
230
223
|
return [
|
231
224
|
Workspace.model_validate(e)
|
232
|
-
for e in await self.
|
225
|
+
for e in await self._http.get_paginated(
|
233
226
|
"https://api.anaplan.com/2/0/workspaces", "workspaces", params=params
|
234
227
|
)
|
235
228
|
]
|
@@ -248,7 +241,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
248
241
|
params["s"] = search_pattern
|
249
242
|
return [
|
250
243
|
Model.model_validate(e)
|
251
|
-
for e in await self.
|
244
|
+
for e in await self._http.get_paginated(
|
252
245
|
"https://api.anaplan.com/2/0/models", "models", params=params
|
253
246
|
)
|
254
247
|
]
|
@@ -261,7 +254,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
261
254
|
:return:
|
262
255
|
"""
|
263
256
|
logger.info(f"Deleting Models: {', '.join(model_ids)}.")
|
264
|
-
res = await self.
|
257
|
+
res = await self._http.post(
|
265
258
|
f"https://api.anaplan.com/2/0/workspaces/{self._workspace_id}/bulkDeleteModels",
|
266
259
|
json={"modelIdsToDelete": model_ids},
|
267
260
|
)
|
@@ -273,7 +266,8 @@ class AsyncClient(_AsyncBaseClient):
|
|
273
266
|
:return: The List of Files.
|
274
267
|
"""
|
275
268
|
return [
|
276
|
-
File.model_validate(e)
|
269
|
+
File.model_validate(e)
|
270
|
+
for e in await self._http.get_paginated(f"{self._url}/files", "files")
|
277
271
|
]
|
278
272
|
|
279
273
|
async def get_actions(self) -> list[Action]:
|
@@ -285,7 +279,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
285
279
|
"""
|
286
280
|
return [
|
287
281
|
Action.model_validate(e)
|
288
|
-
for e in await self.
|
282
|
+
for e in await self._http.get_paginated(f"{self._url}/actions", "actions")
|
289
283
|
]
|
290
284
|
|
291
285
|
async def get_processes(self) -> list[Process]:
|
@@ -295,7 +289,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
295
289
|
"""
|
296
290
|
return [
|
297
291
|
Process.model_validate(e)
|
298
|
-
for e in await self.
|
292
|
+
for e in await self._http.get_paginated(f"{self._url}/processes", "processes")
|
299
293
|
]
|
300
294
|
|
301
295
|
async def get_imports(self) -> list[Import]:
|
@@ -305,7 +299,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
305
299
|
"""
|
306
300
|
return [
|
307
301
|
Import.model_validate(e)
|
308
|
-
for e in await self.
|
302
|
+
for e in await self._http.get_paginated(f"{self._url}/imports", "imports")
|
309
303
|
]
|
310
304
|
|
311
305
|
async def get_exports(self) -> list[Export]:
|
@@ -315,7 +309,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
315
309
|
"""
|
316
310
|
return [
|
317
311
|
Export.model_validate(e)
|
318
|
-
for e in await self.
|
312
|
+
for e in await self._http.get_paginated(f"{self._url}/exports", "exports")
|
319
313
|
]
|
320
314
|
|
321
315
|
async def run_action(self, action_id: int, wait_for_completion: bool = True) -> TaskStatus:
|
@@ -330,18 +324,17 @@ class AsyncClient(_AsyncBaseClient):
|
|
330
324
|
until the task is complete. If False, it will spawn the task and return immediately.
|
331
325
|
"""
|
332
326
|
body = {"localeName": "en_US"}
|
333
|
-
res = await self.
|
327
|
+
res = await self._http.post(
|
328
|
+
f"{self._url}/{action_url(action_id)}/{action_id}/tasks", json=body
|
329
|
+
)
|
334
330
|
task_id = res["task"]["taskId"]
|
335
331
|
logger.info(f"Invoked Action '{action_id}', spawned Task: '{task_id}'.")
|
336
332
|
|
337
333
|
if not wait_for_completion:
|
338
334
|
return TaskStatus.model_validate(await self.get_task_status(action_id, task_id))
|
339
|
-
|
340
|
-
while (status := await self.get_task_status(action_id, task_id)).task_state != "COMPLETE":
|
341
|
-
await sleep(self.status_poll_delay)
|
342
|
-
|
335
|
+
status = await self._http.poll_task(self.get_task_status, action_id, task_id)
|
343
336
|
if status.task_state == "COMPLETE" and not status.result.successful:
|
344
|
-
logger.error(f"Task '{task_id}' completed with errors
|
337
|
+
logger.error(f"Task '{task_id}' completed with errors.")
|
345
338
|
raise AnaplanActionError(f"Task '{task_id}' completed with errors.")
|
346
339
|
|
347
340
|
logger.info(f"Task '{task_id}' of '{action_id}' completed successfully.")
|
@@ -356,11 +349,11 @@ class AsyncClient(_AsyncBaseClient):
|
|
356
349
|
chunk_count = await self._file_pre_check(file_id)
|
357
350
|
logger.info(f"File {file_id} has {chunk_count} chunks.")
|
358
351
|
if chunk_count <= 1:
|
359
|
-
return await self.
|
352
|
+
return await self._http.get_binary(f"{self._url}/files/{file_id}")
|
360
353
|
return b"".join(
|
361
354
|
await gather(
|
362
355
|
*(
|
363
|
-
self.
|
356
|
+
self._http.get_binary(f"{self._url}/files/{file_id}/chunks/{i}")
|
364
357
|
for i in range(chunk_count)
|
365
358
|
)
|
366
359
|
)
|
@@ -380,13 +373,13 @@ class AsyncClient(_AsyncBaseClient):
|
|
380
373
|
chunk_count = await self._file_pre_check(file_id)
|
381
374
|
logger.info(f"File {file_id} has {chunk_count} chunks.")
|
382
375
|
if chunk_count <= 1:
|
383
|
-
yield await self.
|
376
|
+
yield await self._http.get_binary(f"{self._url}/files/{file_id}")
|
384
377
|
return
|
385
378
|
|
386
379
|
for batch_start in range(0, chunk_count, batch_size):
|
387
380
|
batch_chunks = await gather(
|
388
381
|
*(
|
389
|
-
self.
|
382
|
+
self._http.get_binary(f"{self._url}/files/{file_id}/chunks/{i}")
|
390
383
|
for i in range(batch_start, min(batch_start + batch_size, chunk_count))
|
391
384
|
)
|
392
385
|
)
|
@@ -462,21 +455,26 @@ class AsyncClient(_AsyncBaseClient):
|
|
462
455
|
logger.info(
|
463
456
|
f"Completed final upload stream batch of size {len(tasks)} for file {file_id}."
|
464
457
|
)
|
465
|
-
await self.
|
458
|
+
await self._http.post(f"{self._url}/files/{file_id}/complete", json={"id": file_id})
|
466
459
|
logger.info(f"Completed upload stream for '{file_id}'.")
|
467
460
|
|
468
|
-
async def upload_and_import(
|
461
|
+
async def upload_and_import(
|
462
|
+
self, file_id: int, content: str | bytes, action_id: int, wait_for_completion: bool = True
|
463
|
+
) -> TaskStatus:
|
469
464
|
"""
|
470
465
|
Convenience wrapper around `upload_file()` and `run_action()` to upload content to a file
|
471
466
|
and run an import action in one call.
|
472
467
|
:param file_id: The identifier of the file to upload to.
|
473
468
|
:param content: The content to upload. **This Content will be compressed before uploading.
|
474
|
-
|
475
|
-
|
469
|
+
If you are passing the Input as bytes, pass it uncompressed to avoid redundant
|
470
|
+
work.**
|
476
471
|
:param action_id: The identifier of the action to run after uploading the content.
|
472
|
+
:param wait_for_completion: If True, the method will poll the import task status and not
|
473
|
+
return until the task is complete. If False, it will spawn the import task and
|
474
|
+
return immediately.
|
477
475
|
"""
|
478
476
|
await self.upload_file(file_id, content)
|
479
|
-
await self.run_action(action_id)
|
477
|
+
return await self.run_action(action_id, wait_for_completion)
|
480
478
|
|
481
479
|
async def export_and_download(self, action_id: int) -> bytes:
|
482
480
|
"""
|
@@ -496,7 +494,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
496
494
|
"""
|
497
495
|
return [
|
498
496
|
TaskSummary.model_validate(e)
|
499
|
-
for e in await self.
|
497
|
+
for e in await self._http.get_paginated(
|
500
498
|
f"{self._url}/{action_url(action_id)}/{action_id}/tasks", "tasks"
|
501
499
|
)
|
502
500
|
]
|
@@ -510,7 +508,9 @@ class AsyncClient(_AsyncBaseClient):
|
|
510
508
|
"""
|
511
509
|
return TaskStatus.model_validate(
|
512
510
|
(
|
513
|
-
await self.
|
511
|
+
await self._http.get(
|
512
|
+
f"{self._url}/{action_url(action_id)}/{action_id}/tasks/{task_id}"
|
513
|
+
)
|
514
514
|
).get("task")
|
515
515
|
)
|
516
516
|
|
@@ -521,7 +521,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
521
521
|
:param task_id: The Task identifier, sometimes also referred to as the Correlation Id.
|
522
522
|
:return: The content of the solution logs.
|
523
523
|
"""
|
524
|
-
return await self.
|
524
|
+
return await self._http.get_binary(
|
525
525
|
f"{self._url}/optimizeActions/{action_id}/tasks/{task_id}/solutionLogs"
|
526
526
|
)
|
527
527
|
|
@@ -532,7 +532,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
532
532
|
return file.chunk_count
|
533
533
|
|
534
534
|
async def _upload_chunk(self, file_id: int, index: int, chunk: str | bytes) -> None:
|
535
|
-
await self.
|
535
|
+
await self._http.put_binary_gzip(f"{self._url}/files/{file_id}/chunks/{index}", chunk)
|
536
536
|
logger.debug(f"Chunk {index} loaded to file '{file_id}'.")
|
537
537
|
|
538
538
|
async def _set_chunk_count(self, file_id: int, num_chunks: int) -> None:
|
@@ -542,7 +542,9 @@ class AsyncClient(_AsyncBaseClient):
|
|
542
542
|
"to avoid this error, set `allow_file_creation=True` on the calling instance. "
|
543
543
|
"Make sure you have understood the implications of this before doing so. "
|
544
544
|
)
|
545
|
-
response = await self.
|
545
|
+
response = await self._http.post(
|
546
|
+
f"{self._url}/files/{file_id}", json={"chunkCount": num_chunks}
|
547
|
+
)
|
546
548
|
optionally_new_file = int(response.get("file").get("id"))
|
547
549
|
if optionally_new_file != file_id:
|
548
550
|
if self.allow_file_creation:
|