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.
@@ -1,10 +1,7 @@
1
1
  import logging
2
- from asyncio import sleep
3
2
  from typing import Literal, overload
4
3
 
5
- import httpx
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(_AsyncBaseClient):
22
- def __init__(
23
- self, client: httpx.AsyncClient, model_id: str, retry_count: int, status_poll_delay: int
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._put(f"{self._url}/onlineStatus", json={"status": status})
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._get(f"{self._url}/alm/revisions")
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._get(f"{self._url}/alm/latestRevision")).get("revisions")
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._get(f"{self._url}/alm/syncableRevisions?sourceModelId={source_model_id}")
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._post(
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._get(f"{self._url}/alm/syncTasks")
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._get(f"{self._url}/alm/syncTasks/{task_id}")
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._post(f"{self._url}/alm/syncTasks", json=payload)
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
- while (task := await self.get_sync_task(task.id)).task_state != "COMPLETE":
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._get(f"{self._url}/alm/revisions/{revision_id}/appliedToModels")
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._post(f"{self._url}/alm/comparisonReportTasks", json=payload)
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
- while (task := await self.get_comparison_report_task(task.id)).task_state != "COMPLETE":
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._get(f"{self._url}/alm/comparisonReportTasks/{task_id}")
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._get_binary(
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._post(f"{self._url}/alm/summaryReportTasks", json=payload)
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
- while (task := await self.get_comparison_summary_task(task.id)).task_state != "COMPLETE":
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._get(f"{self._url}/alm/summaryReportTasks/{task_id}")
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._get(
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 httpx
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(_AsyncBaseClient):
12
- def __init__(self, client: httpx.AsyncClient, retry_count: int) -> None:
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._get_paginated(
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._get(f"https://api.anaplan.com/2/0/users/{user_id}")).get("user")
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._get_paginated(
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, sleep
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._base import _AsyncBaseClient, action_url
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(_AsyncBaseClient):
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
- _client = httpx.AsyncClient(
97
- auth=(
98
- auth
99
- or _create_auth(
100
- token=token,
101
- user_email=user_email,
102
- password=password,
103
- certificate=certificate,
104
- private_key=private_key,
105
- private_key_password=private_key_password,
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(_client, model_id, retry_count) if model_id else None
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._audit = _AsyncAuditClient(_client, self._retry_count)
123
- self._cloud_works = _AsyncCloudWorksClient(_client, self._retry_count)
124
- self.status_poll_delay = status_poll_delay
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
- existing._client, new_model_id, existing._retry_count
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 transactional(self) -> _AsyncTransactionalClient:
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
- :py:class:`ValueError`, since none of the endpoints can be invoked without the model Id.
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._get_paginated(
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._get_paginated(
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._post(
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) for e in await self._get_paginated(f"{self._url}/files", "files")
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._get_paginated(f"{self._url}/actions", "actions")
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._get_paginated(f"{self._url}/processes", "processes")
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._get_paginated(f"{self._url}/imports", "imports")
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._get_paginated(f"{self._url}/exports", "exports")
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._post(f"{self._url}/{action_url(action_id)}/{action_id}/tasks", json=body)
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: {status.result.error_message}")
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._get_binary(f"{self._url}/files/{file_id}")
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._get_binary(f"{self._url}/files/{file_id}/chunks/{i}")
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._get_binary(f"{self._url}/files/{file_id}")
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._get_binary(f"{self._url}/files/{file_id}/chunks/{i}")
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._post(f"{self._url}/files/{file_id}/complete", json={"id": file_id})
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(self, file_id: int, content: str | bytes, action_id: int) -> None:
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
- If you are passing the Input as bytes, pass it uncompressed to avoid
475
- redundant work.**
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._get_paginated(
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._get(f"{self._url}/{action_url(action_id)}/{action_id}/tasks/{task_id}")
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._get_binary(
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._put_binary_gzip(f"{self._url}/files/{file_id}/chunks/{index}", chunk)
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._post(f"{self._url}/files/{file_id}", json={"chunkCount": num_chunks})
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: