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