anaplan-sdk 0.5.0a1__py3-none-any.whl → 0.5.0a3__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,14 @@ class Client(_BaseClient):
51
50
  auth: httpx.Auth | None = None,
52
51
  timeout: float | httpx.Timeout = 30,
53
52
  retry_count: int = 2,
53
+ backoff: float = 1.0,
54
+ backoff_factor: float = 2.0,
55
+ page_size: int = 5_000,
54
56
  status_poll_delay: int = 1,
55
57
  upload_parallel: bool = True,
56
58
  upload_chunk_size: int = 25_000_000,
57
59
  allow_file_creation: bool = False,
60
+ **httpx_kwargs,
58
61
  ) -> None:
59
62
  """
60
63
  Synchronous Anaplan Client. For guides and examples
@@ -86,7 +89,15 @@ class Client(_BaseClient):
86
89
  an instance of `httpx.Timeout` to set the timeout for the HTTP requests.
87
90
  :param retry_count: The number of times to retry an HTTP request if it fails. Set this to 0
88
91
  to never retry. Defaults to 2, meaning each HTTP Operation will be tried a total
92
+ :param backoff: The initial backoff time in seconds for the retry mechanism. This is the
93
+ time to wait before the first retry.
94
+ :param backoff_factor: The factor by which the backoff time is multiplied after each retry.
95
+ For example, if the initial backoff is 1 second and the factor is 2, the second
96
+ retry will wait 2 seconds, the third retry will wait 4 seconds, and so on.
89
97
  number of 2 times.
98
+ :param page_size: The number of items to return per page when paginating through results.
99
+ Defaults to 5000. This is the maximum number of items that can be returned per
100
+ request. If you pass a value greater than 5000, it will be capped to 5000.
90
101
  :param status_poll_delay: The delay between polling the status of a task.
91
102
  :param upload_parallel: Whether to upload chunks in parallel when uploading files.
92
103
  :param upload_chunk_size: The size of the chunks to upload. This is the maximum size of
@@ -96,41 +107,43 @@ class Client(_BaseClient):
96
107
  altogether. A file that is created this way will not be referenced by any action in
97
108
  anaplan until manually assigned so there is typically no value in dynamically
98
109
  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,
110
+ :param httpx_kwargs: Additional keyword arguments to pass to the `httpx.Client`.
111
+ This can be used to set additional options such as proxies, headers, etc. See
112
+ https://www.python-httpx.org/api/#client for the full list of arguments.
113
+ """
114
+ auth = auth or _create_auth(
115
+ token=token,
116
+ user_email=user_email,
117
+ password=password,
118
+ certificate=certificate,
119
+ private_key=private_key,
120
+ private_key_password=private_key_password,
121
+ )
122
+ _client = httpx.Client(auth=auth, timeout=timeout, **httpx_kwargs)
123
+ self._http = _HttpService(
124
+ _client,
125
+ retry_count=retry_count,
126
+ backoff=backoff,
127
+ backoff_factor=backoff_factor,
128
+ page_size=page_size,
129
+ poll_delay=status_poll_delay,
113
130
  )
114
131
  self._retry_count = retry_count
115
132
  self._workspace_id = workspace_id
116
133
  self._model_id = model_id
117
134
  self._url = f"https://api.anaplan.com/2/0/workspaces/{workspace_id}/models/{model_id}"
118
135
  self._transactional_client = (
119
- _TransactionalClient(_client, model_id, self._retry_count) if model_id else None
136
+ _TransactionalClient(self._http, model_id) if model_id else None
120
137
  )
121
- self._alm_client = (
122
- _AlmClient(_client, model_id, self._retry_count, status_poll_delay)
123
- if model_id
124
- else None
125
- )
126
- self._cloud_works = _CloudWorksClient(_client, self._retry_count)
138
+ self._alm_client = _AlmClient(self._http, model_id) if model_id else None
139
+ self._cloud_works = _CloudWorksClient(self._http)
127
140
  self._thread_count = multiprocessing.cpu_count()
128
- self._audit = _AuditClient(_client, self._retry_count, self._thread_count)
141
+ self._audit = _AuditClient(self._http)
129
142
  self.status_poll_delay = status_poll_delay
130
143
  self.upload_parallel = upload_parallel
131
144
  self.upload_chunk_size = upload_chunk_size
132
145
  self.allow_file_creation = allow_file_creation
133
- super().__init__(self._retry_count, _client)
146
+ logger.debug(f"Initialized Client with workspace_id={workspace_id}, model_id={model_id}")
134
147
 
135
148
  @classmethod
136
149
  def from_existing(
@@ -158,12 +171,8 @@ class Client(_BaseClient):
158
171
  f"with workspace_id={new_ws_id}, model_id={new_model_id}."
159
172
  )
160
173
  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
- )
174
+ client._transactional_client = _TransactionalClient(existing._http, new_model_id)
175
+ client._alm_client = _AlmClient(existing._http, new_model_id)
167
176
  return client
168
177
 
169
178
  @property
@@ -183,14 +192,14 @@ class Client(_BaseClient):
183
192
  return self._cloud_works
184
193
 
185
194
  @property
186
- def transactional(self) -> _TransactionalClient:
195
+ def tr(self) -> _TransactionalClient:
187
196
  """
188
197
  The Transactional Client provides access to the Anaplan Transactional API. This is useful
189
198
  for more advanced use cases where you need to interact with the Anaplan Model in a more
190
199
  granular way.
191
200
 
192
201
  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.
202
+ `ValueError`, since none of the endpoints can be invoked without the model Id.
194
203
  :return: The Transactional Client.
195
204
  """
196
205
  if not self._transactional_client:
@@ -234,7 +243,7 @@ class Client(_BaseClient):
234
243
  params["s"] = search_pattern
235
244
  return [
236
245
  Workspace.model_validate(e)
237
- for e in self._get_paginated(
246
+ for e in self._http.get_paginated(
238
247
  "https://api.anaplan.com/2/0/workspaces", "workspaces", params=params
239
248
  )
240
249
  ]
@@ -253,7 +262,7 @@ class Client(_BaseClient):
253
262
  params["s"] = search_pattern
254
263
  return [
255
264
  Model.model_validate(e)
256
- for e in self._get_paginated(
265
+ for e in self._http.get_paginated(
257
266
  "https://api.anaplan.com/2/0/models", "models", params=params
258
267
  )
259
268
  ]
@@ -266,7 +275,7 @@ class Client(_BaseClient):
266
275
  :return:
267
276
  """
268
277
  logger.info(f"Deleting Models: {', '.join(model_ids)}.")
269
- res = self._post(
278
+ res = self._http.post(
270
279
  f"https://api.anaplan.com/2/0/workspaces/{self._workspace_id}/bulkDeleteModels",
271
280
  json={"modelIdsToDelete": model_ids},
272
281
  )
@@ -277,7 +286,9 @@ class Client(_BaseClient):
277
286
  Lists all the Files in the Model.
278
287
  :return: The List of Files.
279
288
  """
280
- return [File.model_validate(e) for e in self._get_paginated(f"{self._url}/files", "files")]
289
+ return [
290
+ File.model_validate(e) for e in self._http.get_paginated(f"{self._url}/files", "files")
291
+ ]
281
292
 
282
293
  def get_actions(self) -> list[Action]:
283
294
  """
@@ -287,7 +298,8 @@ class Client(_BaseClient):
287
298
  :return: The List of Actions.
288
299
  """
289
300
  return [
290
- Action.model_validate(e) for e in self._get_paginated(f"{self._url}/actions", "actions")
301
+ Action.model_validate(e)
302
+ for e in self._http.get_paginated(f"{self._url}/actions", "actions")
291
303
  ]
292
304
 
293
305
  def get_processes(self) -> list[Process]:
@@ -297,7 +309,7 @@ class Client(_BaseClient):
297
309
  """
298
310
  return [
299
311
  Process.model_validate(e)
300
- for e in self._get_paginated(f"{self._url}/processes", "processes")
312
+ for e in self._http.get_paginated(f"{self._url}/processes", "processes")
301
313
  ]
302
314
 
303
315
  def get_imports(self) -> list[Import]:
@@ -306,7 +318,8 @@ class Client(_BaseClient):
306
318
  :return: The List of Imports.
307
319
  """
308
320
  return [
309
- Import.model_validate(e) for e in self._get_paginated(f"{self._url}/imports", "imports")
321
+ Import.model_validate(e)
322
+ for e in self._http.get_paginated(f"{self._url}/imports", "imports")
310
323
  ]
311
324
 
312
325
  def get_exports(self) -> list[Export]:
@@ -315,7 +328,8 @@ class Client(_BaseClient):
315
328
  :return: The List of Exports.
316
329
  """
317
330
  return [
318
- Export.model_validate(e) for e in (self._get(f"{self._url}/exports")).get("exports", [])
331
+ Export.model_validate(e)
332
+ for e in (self._http.get(f"{self._url}/exports")).get("exports", [])
319
333
  ]
320
334
 
321
335
  def run_action(self, action_id: int, wait_for_completion: bool = True) -> TaskStatus:
@@ -330,18 +344,15 @@ class Client(_BaseClient):
330
344
  until the task is complete. If False, it will spawn the task and return immediately.
331
345
  """
332
346
  body = {"localeName": "en_US"}
333
- res = self._post(f"{self._url}/{action_url(action_id)}/{action_id}/tasks", json=body)
347
+ res = self._http.post(f"{self._url}/{action_url(action_id)}/{action_id}/tasks", json=body)
334
348
  task_id = res["task"]["taskId"]
335
349
  logger.info(f"Invoked Action '{action_id}', spawned Task: '{task_id}'.")
336
350
 
337
351
  if not wait_for_completion:
338
352
  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
-
353
+ status = self._http.poll_task(self.get_task_status, action_id, task_id)
343
354
  if status.task_state == "COMPLETE" and not status.result.successful:
344
- logger.error(f"Task '{task_id}' completed with errors: {status.result.error_message}")
355
+ logger.error(f"Task '{task_id}' completed with errors.")
345
356
  raise AnaplanActionError(f"Task '{task_id}' completed with errors.")
346
357
 
347
358
  logger.info(f"Task '{task_id}' of Action '{action_id}' completed successfully.")
@@ -356,10 +367,10 @@ class Client(_BaseClient):
356
367
  chunk_count = self._file_pre_check(file_id)
357
368
  logger.info(f"File {file_id} has {chunk_count} chunks.")
358
369
  if chunk_count <= 1:
359
- return self._get_binary(f"{self._url}/files/{file_id}")
370
+ return self._http.get_binary(f"{self._url}/files/{file_id}")
360
371
  with ThreadPoolExecutor(max_workers=self._thread_count) as executor:
361
372
  chunks = executor.map(
362
- self._get_binary,
373
+ self._http.get_binary,
363
374
  (f"{self._url}/files/{file_id}/chunks/{i}" for i in range(chunk_count)),
364
375
  )
365
376
  return b"".join(chunks)
@@ -378,13 +389,13 @@ class Client(_BaseClient):
378
389
  chunk_count = self._file_pre_check(file_id)
379
390
  logger.info(f"File {file_id} has {chunk_count} chunks.")
380
391
  if chunk_count <= 1:
381
- yield self._get_binary(f"{self._url}/files/{file_id}")
392
+ yield self._http.get_binary(f"{self._url}/files/{file_id}")
382
393
  return
383
394
 
384
395
  with ThreadPoolExecutor(max_workers=batch_size) as executor:
385
396
  for batch_start in range(0, chunk_count, batch_size):
386
397
  batch_chunks = executor.map(
387
- self._get_binary,
398
+ self._http.get_binary,
388
399
  (
389
400
  f"{self._url}/files/{file_id}/chunks/{i}"
390
401
  for i in range(batch_start, min(batch_start + batch_size, chunk_count))
@@ -458,21 +469,26 @@ class Client(_BaseClient):
458
469
  logger.info(
459
470
  f"Completed final upload stream batch of size {len(indices)} for file {file_id}."
460
471
  )
461
- self._post(f"{self._url}/files/{file_id}/complete", json={"id": file_id})
472
+ self._http.post(f"{self._url}/files/{file_id}/complete", json={"id": file_id})
462
473
  logger.info(f"Completed upload stream for '{file_id}'.")
463
474
 
464
- def upload_and_import(self, file_id: int, content: str | bytes, action_id: int) -> None:
475
+ def upload_and_import(
476
+ self, file_id: int, content: str | bytes, action_id: int, wait_for_completion: bool = True
477
+ ) -> TaskStatus:
465
478
  """
466
479
  Convenience wrapper around `upload_file()` and `run_action()` to upload content to a file
467
480
  and run an import action in one call.
468
481
  :param file_id: The identifier of the file to upload to.
469
482
  :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.**
483
+ If you are passing the Input as bytes, pass it uncompressed to avoid redundant
484
+ work.**
472
485
  :param action_id: The identifier of the action to run after uploading the content.
486
+ :param wait_for_completion: If True, the method will poll the import task status and not
487
+ return until the task is complete. If False, it will spawn the import task and
488
+ return immediately.
473
489
  """
474
490
  self.upload_file(file_id, content)
475
- self.run_action(action_id)
491
+ return self.run_action(action_id, wait_for_completion)
476
492
 
477
493
  def export_and_download(self, action_id: int) -> bytes:
478
494
  """
@@ -492,7 +508,7 @@ class Client(_BaseClient):
492
508
  """
493
509
  return [
494
510
  TaskSummary.model_validate(e)
495
- for e in self._get_paginated(
511
+ for e in self._http.get_paginated(
496
512
  f"{self._url}/{action_url(action_id)}/{action_id}/tasks", "tasks"
497
513
  )
498
514
  ]
@@ -505,7 +521,7 @@ class Client(_BaseClient):
505
521
  :return: The status of the task.
506
522
  """
507
523
  return TaskStatus.model_validate(
508
- self._get(f"{self._url}/{action_url(action_id)}/{action_id}/tasks/{task_id}").get(
524
+ self._http.get(f"{self._url}/{action_url(action_id)}/{action_id}/tasks/{task_id}").get(
509
525
  "task"
510
526
  )
511
527
  )
@@ -517,7 +533,7 @@ class Client(_BaseClient):
517
533
  :param task_id: The Task identifier, sometimes also referred to as the Correlation Id.
518
534
  :return: The content of the solution logs.
519
535
  """
520
- return self._get_binary(
536
+ return self._http.get_binary(
521
537
  f"{self._url}/optimizeActions/{action_id}/tasks/{task_id}/solutionLogs"
522
538
  )
523
539
 
@@ -528,7 +544,7 @@ class Client(_BaseClient):
528
544
  return file.chunk_count
529
545
 
530
546
  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)
547
+ self._http.put_binary_gzip(f"{self._url}/files/{file_id}/chunks/{index}", chunk)
532
548
  logger.debug(f"Chunk {index} loaded to file '{file_id}'.")
533
549
 
534
550
  def _set_chunk_count(self, file_id: int, num_chunks: int) -> None:
@@ -539,7 +555,7 @@ class Client(_BaseClient):
539
555
  "to avoid this error, set `allow_file_creation=True` on the calling instance. "
540
556
  "Make sure you have understood the implications of this before doing so. "
541
557
  )
542
- response = self._post(f"{self._url}/files/{file_id}", json={"chunkCount": num_chunks})
558
+ response = self._http.post(f"{self._url}/files/{file_id}", json={"chunkCount": num_chunks})
543
559
  optionally_new_file = int(response.get("file").get("id"))
544
560
  if optionally_new_file != file_id:
545
561
  if self.allow_file_creation: