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 time import sleep
3
2
  from typing import Literal, overload
4
3
 
5
- import httpx
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(_BaseClient):
22
- def __init__(
23
- self, client: httpx.Client, model_id: str, retry_count: int, status_poll_delay: int
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._put(f"{self._url}/onlineStatus", json={"status": status})
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._get(f"{self._url}/alm/revisions")
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._get(f"{self._url}/alm/latestRevision")).get("revisions")
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._get(f"{self._url}/alm/syncableRevisions?sourceModelId={source_model_id}")
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._post(
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._get(f"{self._url}/alm/syncTasks")
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._get(f"{self._url}/alm/syncTasks/{task_id}")
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._post(f"{self._url}/alm/syncTasks", json=payload)
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
- while (task := self.get_sync_task(task.id)).task_state != "COMPLETE":
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._get(f"{self._url}/alm/revisions/{revision_id}/appliedToModels")
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._post(f"{self._url}/alm/comparisonReportTasks", json=payload)
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
- while (task := self.get_comparison_report_task(task.id)).task_state != "COMPLETE":
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._get(f"{self._url}/alm/comparisonReportTasks/{task_id}")
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._get_binary(
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._post(f"{self._url}/alm/summaryReportTasks", json=payload)
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
- while (task := self.get_comparison_summary_task(task.id)).task_state != "COMPLETE":
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._get(f"{self._url}/alm/summaryReportTasks/{task_id}")
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._get(
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
  )
@@ -1,19 +1,16 @@
1
1
  from typing import Any, Literal
2
2
 
3
- import httpx
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(_BaseClient):
12
- def __init__(self, client: httpx.Client, retry_count: int, thread_count: int) -> None:
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._get_paginated(
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._get(f"https://api.anaplan.com/2/0/users/{user_id}").get("user")
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._get_paginated(
50
+ self._http.get_paginated(
54
51
  self._url,
55
52
  "response",
56
53
  params={"type": event_type, "intervalInHours": days_into_past * 24},
@@ -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._base import _BaseClient, action_url
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(_BaseClient):
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
- _client = httpx.Client(
101
- auth=(
102
- auth
103
- or _create_auth(
104
- token=token,
105
- user_email=user_email,
106
- password=password,
107
- certificate=certificate,
108
- private_key=private_key,
109
- private_key_password=private_key_password,
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(_client, model_id, self._retry_count) if model_id else None
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._cloud_works = _CloudWorksClient(_client, self._retry_count)
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(_client, self._retry_count, self._thread_count)
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
- existing._client, new_model_id, existing._retry_count
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 transactional(self) -> _TransactionalClient:
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
- :py:class:`ValueError`, since none of the endpoints can be invoked without the model Id.
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._get_paginated(
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._get_paginated(
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._post(
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 [File.model_validate(e) for e in self._get_paginated(f"{self._url}/files", "files")]
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) for e in self._get_paginated(f"{self._url}/actions", "actions")
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._get_paginated(f"{self._url}/processes", "processes")
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) for e in self._get_paginated(f"{self._url}/imports", "imports")
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) for e in (self._get(f"{self._url}/exports")).get("exports", [])
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._post(f"{self._url}/{action_url(action_id)}/{action_id}/tasks", json=body)
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: {status.result.error_message}")
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._get_binary(f"{self._url}/files/{file_id}")
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._get_binary,
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._get_binary(f"{self._url}/files/{file_id}")
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._get_binary,
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._post(f"{self._url}/files/{file_id}/complete", json={"id": file_id})
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(self, file_id: int, content: str | bytes, action_id: int) -> None:
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
- If you are passing the Input as bytes, pass it uncompressed to avoid
471
- redundant work.**
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._get_paginated(
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._get(f"{self._url}/{action_url(action_id)}/{action_id}/tasks/{task_id}").get(
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._get_binary(
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._put_binary_gzip(f"{self._url}/files/{file_id}/chunks/{index}", chunk)
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._post(f"{self._url}/files/{file_id}", json={"chunkCount": num_chunks})
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: