anaplan-sdk 0.3.1__py3-none-any.whl → 0.4.0a1__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.
@@ -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}")
@@ -19,7 +19,6 @@ from anaplan_sdk.models import (
19
19
 
20
20
  class _TransactionalClient(_BaseClient):
21
21
  def __init__(self, client: httpx.Client, model_id: str, retry_count: int) -> None:
22
- self._client = client
23
22
  self._url = f"https://api.anaplan.com/2/0/models/{model_id}"
24
23
  super().__init__(retry_count, client)
25
24
 
@@ -0,0 +1,49 @@
1
+ from ._alm import ModelRevision, Revision, SyncTask
2
+ from ._base import AnaplanModel
3
+ from ._bulk import (
4
+ Action,
5
+ Export,
6
+ ExportTypes,
7
+ File,
8
+ Import,
9
+ ImportTypes,
10
+ List,
11
+ ListMetadata,
12
+ Model,
13
+ Process,
14
+ TaskResult,
15
+ TaskResultDetail,
16
+ TaskStatus,
17
+ TaskSummary,
18
+ Workspace,
19
+ )
20
+ from ._transactional import Failure, InsertionResult, LineItem, ListItem, ModelStatus, Module, User
21
+
22
+ __all__ = [
23
+ "AnaplanModel",
24
+ "ExportTypes",
25
+ "ImportTypes",
26
+ "Workspace",
27
+ "Model",
28
+ "ModelStatus",
29
+ "ModelRevision",
30
+ "File",
31
+ "List",
32
+ "ListItem",
33
+ "ListMetadata",
34
+ "Action",
35
+ "Import",
36
+ "Export",
37
+ "Process",
38
+ "Module",
39
+ "LineItem",
40
+ "TaskSummary",
41
+ "TaskResult",
42
+ "TaskResultDetail",
43
+ "TaskStatus",
44
+ "SyncTask",
45
+ "User",
46
+ "Failure",
47
+ "InsertionResult",
48
+ "Revision",
49
+ ]
@@ -0,0 +1,55 @@
1
+ from pydantic import Field
2
+
3
+ from ._base import AnaplanModel
4
+
5
+
6
+ class Revision(AnaplanModel):
7
+ id: str = Field(description="The unique identifier of this revision.")
8
+ name: str = Field(description="The name of this revision.")
9
+ description: str | None = Field(
10
+ None, description="The description of this revision. Not always present."
11
+ )
12
+ created_on: str = Field(description="The creation date of this revision in ISO format.")
13
+ created_by: str = Field(
14
+ description="The unique identifier of the user who created this revision."
15
+ )
16
+ creation_method: str = Field(description="The creation method of this revision.")
17
+ applied_on: str = Field(description="The application date of this revision in ISO format.")
18
+ applied_by: str = Field(
19
+ description="The unique identifier of the user who applied this revision."
20
+ )
21
+
22
+
23
+ class ModelRevision(AnaplanModel):
24
+ id: str = Field(
25
+ validation_alias="modelId",
26
+ description="The unique identifier of the model this revision belongs to.",
27
+ )
28
+ name: str = Field(
29
+ default="",
30
+ validation_alias="modelName",
31
+ description=(
32
+ "The name of the model this revision belongs to. This can be an empty string, when the "
33
+ "calling user does not have access to the model, but is workspace admin in the "
34
+ "workspace."
35
+ ),
36
+ )
37
+ workspace_id: str = Field(
38
+ description="The unique identifier of the workspace this revision belongs to."
39
+ )
40
+ applied_by: str = Field(
41
+ description="The unique identifier of the user who applied this revision."
42
+ )
43
+ applied_on: str = Field(description="The application date of this revision in ISO format.")
44
+ applied_method: str = Field(description="The application method of this revision.")
45
+ deleted: bool | None = Field(
46
+ None,
47
+ validation_alias="modelDeleted",
48
+ description="Whether the model has been deleted or not.",
49
+ )
50
+
51
+
52
+ class SyncTask(AnaplanModel):
53
+ id: str = Field(validation_alias="taskId", description="The unique identifier of this task.")
54
+ task_state: str = Field(description="The state of this task.")
55
+ creation_time: int = Field(description="The creation time of this task.")
@@ -0,0 +1,17 @@
1
+ from pydantic import BaseModel, ConfigDict, field_serializer
2
+ from pydantic.alias_generators import to_camel
3
+
4
+
5
+ class AnaplanModel(BaseModel):
6
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
7
+
8
+ @field_serializer(
9
+ "action_id",
10
+ "file_id",
11
+ "process_id",
12
+ "id",
13
+ when_used="unless-none",
14
+ check_fields=False,
15
+ ) # While these are of type int, they are serialized as strings in the API payloads
16
+ def str_serializer(self, v: int) -> str:
17
+ return str(v)