anaplan-sdk 0.3.1b1__py3-none-any.whl → 0.4.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,18 +1,14 @@
1
- """
2
- Synchronous Client.
3
- """
4
-
5
1
  import logging
6
2
  import multiprocessing
7
3
  import time
8
4
  from concurrent.futures import ThreadPoolExecutor
9
5
  from copy import copy
10
- from typing import Iterator
6
+ from typing import Callable, Iterator
11
7
 
12
8
  import httpx
13
9
  from typing_extensions import Self
14
10
 
15
- from anaplan_sdk._auth import AnaplanBasicAuth, AnaplanCertAuth, get_certificate, get_private_key
11
+ from anaplan_sdk._auth import create_auth
16
12
  from anaplan_sdk._base import _BaseClient, action_url
17
13
  from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierException
18
14
  from anaplan_sdk.models import (
@@ -29,6 +25,7 @@ from anaplan_sdk.models import (
29
25
 
30
26
  from ._alm import _AlmClient
31
27
  from ._audit import _AuditClient
28
+ from ._cloud_works import _CloudWorksClient
32
29
  from ._transactional import _TransactionalClient
33
30
 
34
31
  logging.getLogger("httpx").setLevel(logging.CRITICAL)
@@ -56,7 +53,13 @@ class Client(_BaseClient):
56
53
  certificate: str | bytes | None = None,
57
54
  private_key: str | bytes | None = None,
58
55
  private_key_password: str | bytes | None = None,
59
- timeout: float = 30,
56
+ client_id: str | None = None,
57
+ client_secret: str | None = None,
58
+ redirect_uri: str | None = None,
59
+ refresh_token: str | None = None,
60
+ oauth2_scope: str = "openid profile email offline_access",
61
+ on_token_refresh: Callable[[dict[str, str]], None] | None = None,
62
+ timeout: float | httpx.Timeout = 30,
60
63
  retry_count: int = 2,
61
64
  status_poll_delay: int = 1,
62
65
  upload_parallel: bool = True,
@@ -85,7 +88,19 @@ class Client(_BaseClient):
85
88
  itself.
86
89
  :param private_key: The absolute path to the private key file or the private key itself.
87
90
  :param private_key_password: The password to access the private key if there is one.
88
- :param timeout: The timeout in seconds for the HTTP requests.
91
+ :param client_id: The client Id of the Oauth2 Anaplan Client.
92
+ :param client_secret: The client secret for your Oauth2 Anaplan Client.
93
+ :param redirect_uri: The redirect URI for your Oauth2 Anaplan Client.
94
+ :param refresh_token: If you have a valid refresh token, you can pass it to skip the
95
+ interactive authentication code step.
96
+ :param oauth2_scope: The scope of the Oauth2 token, if you want to narrow it.
97
+ :param on_token_refresh: A callback function that is called whenever the token is refreshed.
98
+ With this you can for example securely store the token in your
99
+ application or on your server for later reuse. The function
100
+ must accept a single argument, which is the token dictionary
101
+ returned by the Oauth2 token endpoint.
102
+ :param timeout: The timeout in seconds for the HTTP requests. Alternatively, you can pass
103
+ an instance of `httpx.Timeout` to set the timeout for the HTTP requests.
89
104
  :param retry_count: The number of times to retry an HTTP request if it fails. Set this to 0
90
105
  to never retry. Defaults to 2, meaning each HTTP Operation will be
91
106
  tried a total number of 2 times.
@@ -102,36 +117,38 @@ class Client(_BaseClient):
102
117
  manually assigned so there is typically no value in dynamically
103
118
  creating new files and uploading content to them.
104
119
  """
105
- if not ((user_email and password) or (certificate and private_key)):
106
- raise ValueError(
107
- "Either `certificate` and `private_key` or `user_email` and `password` must be "
108
- "provided."
109
- )
110
- self._client = httpx.Client(
120
+ _client = httpx.Client(
111
121
  auth=(
112
- AnaplanCertAuth(
113
- get_certificate(certificate), get_private_key(private_key, private_key_password)
122
+ create_auth(
123
+ user_email=user_email,
124
+ password=password,
125
+ certificate=certificate,
126
+ private_key=private_key,
127
+ private_key_password=private_key_password,
128
+ client_id=client_id,
129
+ client_secret=client_secret,
130
+ redirect_uri=redirect_uri,
131
+ refresh_token=refresh_token,
132
+ oauth2_scope=oauth2_scope,
133
+ on_token_refresh=on_token_refresh,
114
134
  )
115
- if certificate
116
- else AnaplanBasicAuth(user_email=user_email, password=password)
117
135
  ),
118
136
  timeout=timeout,
119
137
  )
120
138
  self._retry_count = retry_count
121
139
  self._url = f"https://api.anaplan.com/2/0/workspaces/{workspace_id}/models/{model_id}"
122
140
  self._transactional_client = (
123
- _TransactionalClient(self._client, model_id, self._retry_count) if model_id else None
124
- )
125
- self._alm_client = (
126
- _AlmClient(self._client, model_id, self._retry_count) if model_id else None
141
+ _TransactionalClient(_client, model_id, self._retry_count) if model_id else None
127
142
  )
143
+ self._alm_client = _AlmClient(_client, model_id, self._retry_count) if model_id else None
144
+ self._cloud_works = _CloudWorksClient(_client, self._retry_count)
128
145
  self._thread_count = multiprocessing.cpu_count()
129
- self.audit = _AuditClient(self._client, self._retry_count, self._thread_count)
146
+ self._audit = _AuditClient(_client, self._retry_count, self._thread_count)
130
147
  self.status_poll_delay = status_poll_delay
131
148
  self.upload_parallel = upload_parallel
132
149
  self.upload_chunk_size = upload_chunk_size
133
150
  self.allow_file_creation = allow_file_creation
134
- super().__init__(self._retry_count, self._client)
151
+ super().__init__(self._retry_count, _client)
135
152
 
136
153
  @classmethod
137
154
  def from_existing(cls, existing: Self, workspace_id: str, model_id: str) -> Self:
@@ -153,6 +170,22 @@ class Client(_BaseClient):
153
170
  client._alm_client = _AlmClient(existing._client, model_id, existing._retry_count)
154
171
  return client
155
172
 
173
+ @property
174
+ def audit(self) -> _AuditClient:
175
+ """
176
+ The Audit Client provides access to the Anaplan Audit API.
177
+ For details, see https://vinzenzklass.github.io/anaplan-sdk/guides/audit/.
178
+ """
179
+ return self._audit
180
+
181
+ @property
182
+ def cw(self) -> _CloudWorksClient:
183
+ """
184
+ The Cloud Works Client provides access to the Anaplan Cloud Works API.
185
+ For details, see https://vinzenzklass.github.io/anaplan-sdk/guides/cloud_works/.
186
+ """
187
+ return self._cloud_works
188
+
156
189
  @property
157
190
  def transactional(self) -> _TransactionalClient:
158
191
  """
@@ -192,29 +225,38 @@ class Client(_BaseClient):
192
225
  )
193
226
  return self._alm_client
194
227
 
195
- def list_workspaces(self) -> list[Workspace]:
228
+ def list_workspaces(self, search_pattern: str | None = None) -> list[Workspace]:
196
229
  """
197
230
  Lists all the Workspaces the authenticated user has access to.
231
+ :param search_pattern: Optional filter for workspaces. When provided, case-insensitive
232
+ matches workspaces with names containing this string. When None (default),
233
+ returns all workspaces.
198
234
  :return: The List of Workspaces.
199
235
  """
236
+ params = {"tenantDetails": "true"}
237
+ if search_pattern:
238
+ params["s"] = search_pattern
200
239
  return [
201
240
  Workspace.model_validate(e)
202
241
  for e in self._get_paginated(
203
- "https://api.anaplan.com/2/0/workspaces",
204
- "workspaces",
205
- params={"tenantDetails": "true"},
242
+ "https://api.anaplan.com/2/0/workspaces", "workspaces", params=params
206
243
  )
207
244
  ]
208
245
 
209
- def list_models(self) -> list[Model]:
246
+ def list_models(self, search_pattern: str | None = None) -> list[Model]:
210
247
  """
211
248
  Lists all the Models the authenticated user has access to.
249
+ :param search_pattern: Optional filter for models. When provided, case-insensitive matches
250
+ models with names containing this string. When None (default), returns all models.
212
251
  :return: The List of Models.
213
252
  """
253
+ params = {"modelDetails": "true"}
254
+ if search_pattern:
255
+ params["s"] = search_pattern
214
256
  return [
215
257
  Model.model_validate(e)
216
258
  for e in self._get_paginated(
217
- "https://api.anaplan.com/2/0/models", "models", params={"modelDetails": "true"}
259
+ "https://api.anaplan.com/2/0/models", "models", params=params
218
260
  )
219
261
  ]
220
262
 
@@ -0,0 +1,342 @@
1
+ from typing import Any, Literal
2
+
3
+ import httpx
4
+
5
+ from anaplan_sdk._base import (
6
+ _BaseClient,
7
+ connection_body_payload,
8
+ construct_payload,
9
+ integration_payload,
10
+ schedule_payload,
11
+ )
12
+ from anaplan_sdk.models.cloud_works import (
13
+ Connection,
14
+ ConnectionBody,
15
+ ConnectionInput,
16
+ Integration,
17
+ IntegrationInput,
18
+ IntegrationProcessInput,
19
+ NotificationConfig,
20
+ NotificationInput,
21
+ RunError,
22
+ RunStatus,
23
+ RunSummary,
24
+ ScheduleInput,
25
+ SingleIntegration,
26
+ )
27
+
28
+ from ._cw_flow import _FlowClient
29
+
30
+
31
+ class _CloudWorksClient(_BaseClient):
32
+ def __init__(self, client: httpx.Client, retry_count: int) -> None:
33
+ self._url = "https://api.cloudworks.anaplan.com/2/0/integrations"
34
+ self._flow = _FlowClient(client, retry_count)
35
+ super().__init__(retry_count, client)
36
+
37
+ @property
38
+ def flows(self) -> _FlowClient:
39
+ """
40
+ Access the Integration Flow APIs.
41
+ """
42
+ return self._flow
43
+
44
+ def list_connections(self) -> list[Connection]:
45
+ """
46
+ List all Connections available in CloudWorks.
47
+ :return: A list of connections.
48
+ """
49
+ return [
50
+ Connection.model_validate(e)
51
+ for e in self._get_paginated(f"{self._url}/connections", "connections")
52
+ ]
53
+
54
+ def create_connection(self, con_info: ConnectionInput | dict[str, Any]) -> str:
55
+ """
56
+ Create a new connection in CloudWorks.
57
+ :param con_info: The connection information. This can be a ConnectionInput instance or a
58
+ dictionary as per the documentation. If a dictionary is passed, it will be validated
59
+ against the ConnectionInput model before sending the request.
60
+ :return: The ID of the new connection.
61
+ """
62
+ res = self._post(
63
+ f"{self._url}/connections", json=construct_payload(ConnectionInput, con_info)
64
+ )
65
+ return res["connections"]["connectionId"]
66
+
67
+ def update_connection(self, con_id: str, con_info: ConnectionBody | dict[str, Any]) -> None:
68
+ """
69
+ Update an existing connection in CloudWorks.
70
+ :param con_id: The ID of the connection to update.
71
+ :param con_info: The name and details of the connection. You must pass all the same details
72
+ as when initially creating the connection again. If you want to update only some of
73
+ the details, use the `patch_connection` method instead.
74
+ """
75
+ self._put(f"{self._url}/connections/{con_id}", json=connection_body_payload(con_info))
76
+
77
+ def patch_connection(self, con_id: str, body: dict[str, Any]) -> None:
78
+ """
79
+ Update an existing connection in CloudWorks.
80
+ :param con_id: The ID of the connection to update.
81
+ :param body: The name and details of the connection. You can pass all the same details as
82
+ when initially creating the connection again, or just any one of them.
83
+ """
84
+ self._patch(f"{self._url}/connections/{con_id}", json=body)
85
+
86
+ def delete_connection(self, con_id: str) -> None:
87
+ """
88
+ Delete an existing connection in CloudWorks.
89
+ :param con_id: The ID of the connection to delete.
90
+ """
91
+ self._delete(f"{self._url}/connections/{con_id}")
92
+
93
+ def list_integrations(
94
+ self, sort_by_name: Literal["ascending", "descending"] = "ascending"
95
+ ) -> list[Integration]:
96
+ """
97
+ List all integrations in CloudWorks.
98
+ :param sort_by_name: Sort the integrations by name in ascending or descending order.
99
+ :return: A list of integrations.
100
+ """
101
+ params = {"sortBy": "name" if sort_by_name == "ascending" else "-name"}
102
+ return [
103
+ Integration.model_validate(e)
104
+ for e in self._get_paginated(f"{self._url}", "integrations", params=params)
105
+ ]
106
+
107
+ def get_integration(self, integration_id: str) -> SingleIntegration:
108
+ """
109
+ Get the details of a specific integration in CloudWorks.
110
+
111
+ **Note: This will not include the integration type! While present when listing integrations,
112
+ the integration type is not included in the details of a single integration.**
113
+ :param integration_id: The ID of the integration to retrieve.
114
+ :return: The details of the integration, without the integration type.
115
+ """
116
+ return SingleIntegration.model_validate(
117
+ (self._get(f"{self._url}/{integration_id}"))["integration"]
118
+ )
119
+
120
+ def create_integration(
121
+ self, body: IntegrationInput | IntegrationProcessInput | dict[str, Any]
122
+ ) -> str:
123
+ """
124
+ Create a new integration in CloudWorks. If not specified, the integration type will be
125
+ either "Import" or "Export" based on the source and target you provide.
126
+
127
+ If you want to instead create a process Integration, you can do so by specifying
128
+ the `process_id` parameter and passing several jobs. **Be careful to ensure, that all ids
129
+ specified in the job inputs match what is defined in your model and matches the process.**
130
+ If this is not the case, this will error, occasionally with a misleading error message,
131
+ i.e. `XYZ is not defined in your model` even though it is, Anaplan just does not know what
132
+ to do with it in the location you specified.
133
+
134
+ You can also use CloudWorks Integrations to simply schedule a process. To do this, you
135
+ can simply pass an IntegrationProcessInput instance with the process_id and no jobs. This
136
+ will create a process integration that will run the process you specified.
137
+ :param body: The integration information. This can be an
138
+ IntegrationInput | IntegrationProcessInput instance or a dictionary as per the
139
+ documentation. If a dictionary is passed, it will be validated against the
140
+ IntegrationInput model before sending the request.
141
+ :return: The ID of the new integration.
142
+ """
143
+ json = integration_payload(body)
144
+ return (self._post(f"{self._url}", json=json))["integration"]["integrationId"]
145
+
146
+ def update_integration(
147
+ self, integration_id: str, body: IntegrationInput | IntegrationProcessInput | dict[str, Any]
148
+ ) -> None:
149
+ """
150
+ Update an existing integration in CloudWorks.
151
+ :param integration_id: The ID of the integration to update.
152
+ :param body: The name and details of the integration. You must pass all the same details
153
+ as when initially creating the integration again. If you want to update only some
154
+ of the details, use the `patch_integration` method instead.
155
+ """
156
+ json = integration_payload(body)
157
+ self._put(f"{self._url}/{integration_id}", json=json)
158
+
159
+ def run_integration(self, integration_id: str) -> str:
160
+ """
161
+ Run an integration in CloudWorks.
162
+ :param integration_id: The ID of the integration to run.
163
+ :return: The ID of the run instance.
164
+ """
165
+ return (self._post_empty(f"{self._url}/{integration_id}/run"))["run"]["id"]
166
+
167
+ def delete_integration(self, integration_id: str) -> None:
168
+ """
169
+ Delete an existing integration in CloudWorks.
170
+ :param integration_id: The ID of the integration to delete.
171
+ """
172
+ self._delete(f"{self._url}/{integration_id}")
173
+
174
+ def get_run_history(self, integration_id: str) -> list[RunSummary]:
175
+ """
176
+ Get the run history of a specific integration in CloudWorks.
177
+ :param integration_id: The ID of the integration to retrieve the run history for.
178
+ :return: A list of run statuses.
179
+ """
180
+ return [
181
+ RunSummary.model_validate(e)
182
+ for e in (self._get(f"{self._url}/runs/{integration_id}"))["history_of_runs"].get(
183
+ "runs", []
184
+ )
185
+ ]
186
+
187
+ def get_run_status(self, run_id: str) -> RunStatus:
188
+ """
189
+ Get the status of a specific run in CloudWorks.
190
+ :param run_id: The ID of the run to retrieve.
191
+ :return: The details of the run.
192
+ """
193
+ return RunStatus.model_validate((self._get(f"{self._url}/run/{run_id}"))["run"])
194
+
195
+ def get_run_error(self, run_id: str) -> RunError | None:
196
+ """
197
+ Get the error details of a specific run in CloudWorks. This exposes potential underlying
198
+ errors like the error of the invoked action, failure dumps and other details.
199
+ :param run_id: The ID of the run to retrieve.
200
+ :return: The details of the run error.
201
+ """
202
+ run = self._get(f"{self._url}/runerror/{run_id}")
203
+ return RunError.model_validate(run["runs"]) if run.get("runs") else None
204
+
205
+ def create_schedule(
206
+ self, integration_id: str, schedule: ScheduleInput | dict[str, Any]
207
+ ) -> None:
208
+ """
209
+ Schedule an integration in CloudWorks.
210
+ :param integration_id: The ID of the integration to schedule.
211
+ :param schedule: The schedule information. This can be a ScheduleInput instance or a
212
+ dictionary as per the documentation. If a dictionary is passed, it will be validated
213
+ against the ScheduleInput model before sending the request.
214
+ """
215
+ self._post(
216
+ f"{self._url}/{integration_id}/schedule",
217
+ json=schedule_payload(integration_id, schedule),
218
+ )
219
+
220
+ def update_schedule(
221
+ self, integration_id: str, schedule: ScheduleInput | dict[str, Any]
222
+ ) -> None:
223
+ """
224
+ Update an integration Schedule in CloudWorks. A schedule must already exist.
225
+ :param integration_id: The ID of the integration to schedule.
226
+ :param schedule: The schedule information. This can be a ScheduleInput instance or a
227
+ dictionary as per the documentation. If a dictionary is passed, it will be validated
228
+ against the ScheduleInput model before sending the request.
229
+ """
230
+ self._put(
231
+ f"{self._url}/{integration_id}/schedule",
232
+ json=schedule_payload(integration_id, schedule),
233
+ )
234
+
235
+ def set_schedule_status(
236
+ self, integration_id: str, status: Literal["enabled", "disabled"]
237
+ ) -> None:
238
+ """
239
+ Set the status of an integration schedule in CloudWorks. A schedule must already exist.
240
+ :param integration_id: The ID of the integration to schedule.
241
+ :param status: The status of the schedule. This can be either "enabled" or "disabled".
242
+ """
243
+ self._post_empty(f"{self._url}/{integration_id}/schedule/status/{status}")
244
+
245
+ def delete_schedule(self, integration_id: str) -> None:
246
+ """
247
+ Delete an integration schedule in CloudWorks. A schedule must already exist.
248
+ :param integration_id: The ID of the integration to schedule.
249
+ """
250
+ self._delete(f"{self._url}/{integration_id}/schedule")
251
+
252
+ def get_notification_config(
253
+ self, notification_id: str | None = None, integration_id: str | None = None
254
+ ) -> NotificationConfig:
255
+ """
256
+ Get the notification configuration, either by its Id, or the notification configuration
257
+ for a specific integration. If the integration_id is specified, the notification_id
258
+ will be ignored.
259
+ :param notification_id: The ID of the notification configuration to retrieve.
260
+ :param integration_id: The ID of the integration to retrieve the notification
261
+ configuration for.
262
+ :return: The details of the notification configuration.
263
+ """
264
+ if not (notification_id or integration_id):
265
+ raise ValueError("Either notification_id or integration_id must be specified.")
266
+ if integration_id:
267
+ notification_id = (self.get_integration(integration_id)).notification_id
268
+ return NotificationConfig.model_validate(
269
+ (self._get(f"{self._url}/notification/{notification_id}"))["notifications"]
270
+ )
271
+
272
+ def create_notification_config(self, config: NotificationInput | dict[str, Any]) -> str:
273
+ """
274
+ Create a notification configuration for an integration in CloudWorks. This will error if
275
+ there is already a notification configuration for the integration, which is also the case
276
+ by default. In this case, you will want to use the `update_notification_config` method
277
+ instead, to partially update the existing configuration or overwrite it.
278
+ :param config: The notification configuration. This can be a NotificationInput instance or
279
+ a dictionary as per the documentation. If a dictionary is passed, it will be
280
+ validated against the NotificationConfig model before sending the request.
281
+ :return: The ID of the new notification configuration.
282
+ """
283
+ res = self._post(
284
+ f"{self._url}/notification", json=construct_payload(NotificationInput, config)
285
+ )
286
+ return res["notification"]["notificationId"]
287
+
288
+ def update_notification_config(
289
+ self, notification_id: str, config: NotificationInput | dict[str, Any]
290
+ ) -> None:
291
+ """
292
+ Update a notification configuration for an integration in CloudWorks. You cannot pass empty
293
+ values or nulls to any of the fields If you want to for e.g. override an existing list of
294
+ users with an empty one, you must delete the notification configuration and create a new
295
+ one with only the values you want to keep.
296
+ :param notification_id: The ID of the notification configuration to update.
297
+ :param config: The notification configuration. This can be a NotificationInput instance or
298
+ a dictionary as per the documentation. If a dictionary is passed, it will be
299
+ validated against the NotificationConfig model before sending the request.
300
+ """
301
+ self._put(
302
+ f"{self._url}/notification/{notification_id}",
303
+ json=construct_payload(NotificationInput, config),
304
+ )
305
+
306
+ def delete_notification_config(
307
+ self, notification_id: str | None = None, integration_id: str | None = None
308
+ ) -> None:
309
+ """
310
+ Delete a notification configuration for an integration in CloudWorks, either by its Id, or
311
+ the notification configuration for a specific integration. If the integration_id is
312
+ specified, the notification_id will be ignored.
313
+ :param notification_id: The ID of the notification configuration to delete.
314
+ :param integration_id: The ID of the integration to delete the notification config of.
315
+ """
316
+ if not (notification_id or integration_id):
317
+ raise ValueError("Either notification_id or integration_id must be specified.")
318
+ if integration_id:
319
+ notification_id = (self.get_integration(integration_id)).notification_id
320
+ self._delete(f"{self._url}/notification/{notification_id}")
321
+
322
+ def get_import_error_dump(self, run_id: str) -> bytes:
323
+ """
324
+ Get the error dump of a specific import run in CloudWorks. Calling this for a run_id that
325
+ did not generate any failure dumps will produce an error.
326
+
327
+ **Note that if you need the error dump of an action in a process, you must use the
328
+ `get_process_error_dump` method instead.**
329
+ :param run_id: The ID of the run to retrieve.
330
+ :return: The error dump.
331
+ """
332
+ return self._get_binary(f"{self._url}/run/{run_id}/dump")
333
+
334
+ def get_process_error_dump(self, run_id: str, action_id: int | str) -> bytes:
335
+ """
336
+ Get the error dump of a specific import run in CloudWorks, that is part of a process.
337
+ Calling this for a run_id that did not generate any failure dumps will produce an error.
338
+ :param run_id: The ID of the run to retrieve.
339
+ :param action_id: The ID of the action to retrieve. This can be found in the RunError.
340
+ :return: The error dump.
341
+ """
342
+ return self._get_binary(f"{self._url}/run/{run_id}/process/import/{action_id}/dumps")
@@ -0,0 +1,78 @@
1
+ from typing import Any
2
+
3
+ import httpx
4
+
5
+ from anaplan_sdk._base import _BaseClient, construct_payload
6
+ from anaplan_sdk.models.flows import Flow, FlowInput, FlowSummary
7
+
8
+
9
+ class _FlowClient(_BaseClient):
10
+ def __init__(self, client: httpx.Client, retry_count: int) -> None:
11
+ self._url = "https://api.cloudworks.anaplan.com/2/0/integrationflows"
12
+ super().__init__(retry_count, client)
13
+
14
+ def list_flows(self, current_user_only: bool = False) -> list[FlowSummary]:
15
+ """
16
+ List all flows in CloudWorks.
17
+ :param current_user_only: Filters the flows to only those created by the current user.
18
+ :return: A list of FlowSummaries.
19
+ """
20
+ params = {"myIntegrations": 1 if current_user_only else 0}
21
+ return [
22
+ FlowSummary.model_validate(e)
23
+ for e in self._get_paginated(self._url, "integrationFlows", page_size=25, params=params)
24
+ ]
25
+
26
+ def get_flow(self, flow_id: str) -> Flow:
27
+ """
28
+ Get a flow by its ID. This returns the full flow object, including the contained steps and
29
+ continuation behavior.
30
+ :param flow_id: The ID of the flow to get.
31
+ :return: The Flow object.
32
+ """
33
+ return Flow.model_validate((self._get(f"{self._url}/{flow_id}"))["integrationFlow"])
34
+
35
+ def run_flow(self, flow_id: str, only_steps: list[str] = None) -> str:
36
+ """
37
+ Run a flow by its ID. Make sure that neither the flow nor any of its contained are running.
38
+ If this is the case, the task will error. Anaplan neither schedules these tasks nor can it
39
+ handle concurrent executions.
40
+ :param flow_id: The ID of the flow to run.
41
+ :param only_steps: A list of step IDs to run. If not provided, only these will be run.
42
+ :return: The ID of the run.
43
+ """
44
+ url = f"{self._url}/{flow_id}/run"
45
+ res = (
46
+ self._post(url, json={"stepsToRun": only_steps})
47
+ if only_steps
48
+ else self._post_empty(url)
49
+ )
50
+ return res["run"]["id"]
51
+
52
+ def create_flow(self, flow: FlowInput | dict[str, Any]) -> str:
53
+ """
54
+ Create a new flow in CloudWorks. Be careful not to omit the `depends_on` field. Anaplan
55
+ will accept these values, but an invalid, corrupted flow will be created, as all Flows must
56
+ have at least 2 Steps, and they must always be sequential
57
+ :param flow: The flow to create. This can be a FlowInput object or a dictionary.
58
+ :return: The ID of the created flow.
59
+ """
60
+ res = self._post(self._url, json=construct_payload(FlowInput, flow))
61
+ return res["integrationFlow"]["integrationFlowId"]
62
+
63
+ def update_flow(self, flow_id: str, flow: FlowInput | dict[str, Any]) -> None:
64
+ """
65
+ Update a flow in CloudWorks. You must provide the full flow object, partial updates are not
66
+ supported.
67
+ :param flow_id: The ID of the flow to update.
68
+ :param flow: The flow to update. This can be a FlowInput object or a dictionary.
69
+ """
70
+ self._put(f"{self._url}/{flow_id}", json=construct_payload(FlowInput, flow))
71
+
72
+ def delete_flow(self, flow_id: str) -> None:
73
+ """
74
+ Delete a flow in CloudWorks. This will not delete its contained steps. This will fail if
75
+ the flow is running or if it has any running steps.
76
+ :param flow_id: The ID of the flow to delete.
77
+ """
78
+ self._delete(f"{self._url}/{flow_id}")
@@ -1,4 +1,3 @@
1
- import warnings
2
1
  from concurrent.futures import ThreadPoolExecutor
3
2
  from itertools import chain
4
3
  from typing import Any
@@ -19,7 +18,6 @@ from anaplan_sdk.models import (
19
18
 
20
19
  class _TransactionalClient(_BaseClient):
21
20
  def __init__(self, client: httpx.Client, model_id: str, retry_count: int) -> None:
22
- self._client = client
23
21
  self._url = f"https://api.anaplan.com/2/0/models/{model_id}"
24
22
  super().__init__(retry_count, client)
25
23
 
@@ -165,23 +163,3 @@ class _TransactionalClient(_BaseClient):
165
163
  """
166
164
  res = self._post(f"{self._url}/modules/{module_id}/data", json=data)
167
165
  return res if "failures" in res else res["numberOfCellsChanged"]
168
-
169
- def write_to_module(self, module_id: int, data: list[dict[str, Any]]) -> int | dict[str, Any]:
170
- warnings.warn(
171
- "`write_to_module()` is deprecated and will be removed in a future version. "
172
- "Use `update_module_data()` instead.",
173
- DeprecationWarning,
174
- stacklevel=1,
175
- )
176
- return self.update_module_data(module_id, data)
177
-
178
- def add_items_to_list(
179
- self, list_id: int, items: list[dict[str, str | int | dict]]
180
- ) -> InsertionResult:
181
- warnings.warn(
182
- "`add_items_to_list()` is deprecated and will be removed in a future version. "
183
- "Use `insert_list_items()` instead.",
184
- DeprecationWarning,
185
- stacklevel=1,
186
- )
187
- return self.insert_list_items(list_id, items)