anaplan-sdk 0.2.5__tar.gz → 0.2.7__tar.gz
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.
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/PKG-INFO +1 -1
- anaplan_sdk-0.2.7/anaplan_sdk/_async_clients/__init__.py +6 -0
- anaplan_sdk-0.2.7/anaplan_sdk/_async_clients/_audit.py +69 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_async_clients/_bulk.py +13 -1
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_base.py +8 -4
- anaplan_sdk-0.2.7/anaplan_sdk/_clients/__init__.py +6 -0
- anaplan_sdk-0.2.7/anaplan_sdk/_clients/_audit.py +68 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_clients/_bulk.py +10 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/docs/anaplan_explained.md +13 -14
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/alm_client.md +1 -1
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/async_alm_client.md +1 -1
- anaplan_sdk-0.2.7/docs/api/async_audit_client.md +5 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/async_transactional_client.md +1 -1
- anaplan_sdk-0.2.7/docs/api/audit_client.md +5 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/transactional_client.md +1 -1
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/alm.md +2 -4
- anaplan_sdk-0.2.7/docs/guides/audit.md +42 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/bulk.md +114 -49
- anaplan_sdk-0.2.7/docs/guides/transactional.md +242 -0
- anaplan_sdk-0.2.7/docs/img/anaplan-overview.webp +0 -0
- anaplan_sdk-0.2.7/docs/index.md +31 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/docs/installation.md +10 -3
- anaplan_sdk-0.2.7/docs/quickstart.md +102 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/mkdocs.yml +17 -14
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/pyproject.toml +3 -2
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/uv.lock +1100 -1129
- anaplan_sdk-0.2.5/anaplan_sdk/_async_clients/__init__.py +0 -5
- anaplan_sdk-0.2.5/anaplan_sdk/_clients/__init__.py +0 -5
- anaplan_sdk-0.2.5/docs/img/anaplan-overview.webp +0 -0
- anaplan_sdk-0.2.5/docs/index.md +0 -31
- anaplan_sdk-0.2.5/docs/quickstart.md +0 -162
- anaplan_sdk-0.2.5/docs/transactional.md +0 -119
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.github/dependabot.yml +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.github/workflows/docs.yml +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.github/workflows/lint.yml +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.github/workflows/tests.yml +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.gitignore +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.pre-commit-config.yaml +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/LICENSE +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/README.md +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/__init__.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_async_clients/_alm.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_async_clients/_transactional.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_auth.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_clients/_alm.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_clients/_transactional.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/exceptions.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/models.py +0 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/async_client.md +0 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/client.md +0 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/exceptions.md +0 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/models.md +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/docs/css/styles.css +0 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/bulk_vs_transactional.md +0 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/logging.md +0 -0
- {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/multiple_models.md +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/docs/img/anaplan-sdk.webp +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_alm_client.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_async_alm_client.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_async_client.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_async_transactional_client.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_client.py +0 -0
- {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_transactional_client.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: anaplan-sdk
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.7
|
4
4
|
Summary: Provides pythonic access to the Anaplan API
|
5
5
|
Project-URL: Homepage, https://vinzenzklass.github.io/anaplan-sdk/
|
6
6
|
Project-URL: Repository, https://github.com/VinzenzKlass/anaplan-sdk
|
@@ -0,0 +1,69 @@
|
|
1
|
+
from asyncio import gather
|
2
|
+
from itertools import chain
|
3
|
+
from math import ceil
|
4
|
+
from typing import Literal
|
5
|
+
|
6
|
+
import httpx
|
7
|
+
|
8
|
+
from anaplan_sdk._base import _AsyncBaseClient
|
9
|
+
|
10
|
+
Event = Literal["all", "byok", "user_activity"]
|
11
|
+
|
12
|
+
|
13
|
+
class _AsyncAuditClient(_AsyncBaseClient):
|
14
|
+
def __init__(self, client: httpx.AsyncClient, retry_count: int) -> None:
|
15
|
+
self._client = client
|
16
|
+
self._limit = 10_000
|
17
|
+
self._url = "https://audit.anaplan.com/audit/api/1/events"
|
18
|
+
super().__init__(retry_count, client)
|
19
|
+
|
20
|
+
async def _get_total(self, days_into_past: int = 60, event_type: Event = "all") -> int:
|
21
|
+
return ( # noqa
|
22
|
+
await self._get(
|
23
|
+
self._url,
|
24
|
+
params={
|
25
|
+
"limit": 0,
|
26
|
+
"type": event_type,
|
27
|
+
"intervalInHours": days_into_past * 24,
|
28
|
+
},
|
29
|
+
)
|
30
|
+
)["meta"]["paging"]["totalSize"] # noqa
|
31
|
+
|
32
|
+
async def _get_result_page(
|
33
|
+
self,
|
34
|
+
days_into_past: int = 60,
|
35
|
+
event_type: Event = "all",
|
36
|
+
limit: int = 10_000,
|
37
|
+
offset: int = 0,
|
38
|
+
) -> list:
|
39
|
+
return (
|
40
|
+
await self._get(
|
41
|
+
self._url,
|
42
|
+
params={
|
43
|
+
"intervalInHours": days_into_past * 24,
|
44
|
+
"limit": limit,
|
45
|
+
"offset": offset,
|
46
|
+
"type": event_type,
|
47
|
+
},
|
48
|
+
)
|
49
|
+
).get("response", [])
|
50
|
+
|
51
|
+
async def get_events(self, days_into_past: int = 30, event_type: Event = "all") -> list:
|
52
|
+
total = await self._get_total(days_into_past, event_type)
|
53
|
+
if total == 0:
|
54
|
+
return []
|
55
|
+
if total <= 10_000:
|
56
|
+
return await self._get_result_page(total)
|
57
|
+
|
58
|
+
return list(
|
59
|
+
chain.from_iterable(
|
60
|
+
await gather(
|
61
|
+
*(
|
62
|
+
self._get_result_page(
|
63
|
+
days_into_past, event_type, self._limit, n * self._limit
|
64
|
+
)
|
65
|
+
for n in range(ceil(total / self._limit))
|
66
|
+
)
|
67
|
+
)
|
68
|
+
)
|
69
|
+
)
|
@@ -17,6 +17,7 @@ from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierExceptio
|
|
17
17
|
from anaplan_sdk.models import Action, Export, File, Import, Model, Process, Workspace
|
18
18
|
|
19
19
|
from ._alm import _AsyncAlmClient
|
20
|
+
from ._audit import _AsyncAuditClient
|
20
21
|
from ._transactional import _AsyncTransactionalClient
|
21
22
|
|
22
23
|
logging.getLogger("httpx").setLevel(logging.CRITICAL)
|
@@ -109,6 +110,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
109
110
|
self._alm_client = (
|
110
111
|
_AsyncAlmClient(self._client, model_id, self._retry_count) if model_id else None
|
111
112
|
)
|
113
|
+
self.audit = _AsyncAuditClient(self._client, self._retry_count)
|
112
114
|
self.status_poll_delay = status_poll_delay
|
113
115
|
self.upload_chunk_size = upload_chunk_size
|
114
116
|
self.allow_file_creation = allow_file_creation
|
@@ -379,7 +381,7 @@ class AsyncClient(_AsyncBaseClient):
|
|
379
381
|
|
380
382
|
async def export_and_download(self, action_id: int) -> bytes:
|
381
383
|
"""
|
382
|
-
Convenience wrapper around `run_action()`
|
384
|
+
Convenience wrapper around `run_action()` and `get_file()` to run an export action and
|
383
385
|
download the exported content in one call.
|
384
386
|
:param action_id: The identifier of the action to run.
|
385
387
|
:return: The content of the exported file.
|
@@ -387,6 +389,16 @@ class AsyncClient(_AsyncBaseClient):
|
|
387
389
|
await self.run_action(action_id)
|
388
390
|
return await self.get_file(action_id)
|
389
391
|
|
392
|
+
async def list_task_status(self, action_id: int) -> list:
|
393
|
+
"""
|
394
|
+
Retrieves the status of all tasks spawned by the specified action.
|
395
|
+
:param action_id: The identifier of the action that was invoked.
|
396
|
+
:return: The list of tasks spawned by the action.
|
397
|
+
"""
|
398
|
+
return (await self._get(f"{self._url}/{action_url(action_id)}/{action_id}/tasks")).get(
|
399
|
+
"tasks", []
|
400
|
+
)
|
401
|
+
|
390
402
|
async def get_task_status(
|
391
403
|
self, action_id: int, task_id: str
|
392
404
|
) -> dict[str, float | int | str | list | dict | bool]:
|
@@ -23,8 +23,8 @@ class _BaseClient:
|
|
23
23
|
self._retry_count = retry_count
|
24
24
|
self._client = client
|
25
25
|
|
26
|
-
def _get(self, url: str) -> dict[str, float | int | str | list | dict | bool]:
|
27
|
-
return self._run_with_retry(self._client.get, url).json()
|
26
|
+
def _get(self, url: str, **kwargs) -> dict[str, float | int | str | list | dict | bool]:
|
27
|
+
return self._run_with_retry(self._client.get, url, **kwargs).json()
|
28
28
|
|
29
29
|
def _get_binary(self, url: str) -> bytes:
|
30
30
|
return self._run_with_retry(self._client.get, url).content
|
@@ -59,14 +59,16 @@ class _BaseClient:
|
|
59
59
|
url = args[0] or kwargs.get("url")
|
60
60
|
logger.info(f"Retrying for: {url}")
|
61
61
|
|
62
|
+
raise AnaplanException("Exhausted all retries without a successful response or Error.")
|
63
|
+
|
62
64
|
|
63
65
|
class _AsyncBaseClient:
|
64
66
|
def __init__(self, retry_count: int, client: httpx.AsyncClient):
|
65
67
|
self._retry_count = retry_count
|
66
68
|
self._client = client
|
67
69
|
|
68
|
-
async def _get(self, url: str) -> dict[str, float | int | str | list | dict | bool]:
|
69
|
-
return (await self._run_with_retry(self._client.get, url)).json()
|
70
|
+
async def _get(self, url: str, **kwargs) -> dict[str, float | int | str | list | dict | bool]:
|
71
|
+
return (await self._run_with_retry(self._client.get, url, **kwargs)).json()
|
70
72
|
|
71
73
|
async def _get_binary(self, url: str) -> bytes:
|
72
74
|
return (await self._run_with_retry(self._client.get, url)).content
|
@@ -105,6 +107,8 @@ class _AsyncBaseClient:
|
|
105
107
|
url = args[0] or kwargs.get("url")
|
106
108
|
logger.info(f"Retrying for: {url}")
|
107
109
|
|
110
|
+
raise AnaplanException("Exhausted all retries without a successful response or Error.")
|
111
|
+
|
108
112
|
|
109
113
|
def action_url(action_id: int) -> Literal["imports", "exports", "actions", "processes"]:
|
110
114
|
"""
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from itertools import chain
|
2
|
+
from math import ceil
|
3
|
+
from typing import Literal
|
4
|
+
|
5
|
+
import httpx
|
6
|
+
|
7
|
+
from anaplan_sdk._base import _BaseClient
|
8
|
+
|
9
|
+
Event = Literal["all", "byok", "user_activity"]
|
10
|
+
|
11
|
+
|
12
|
+
class _AuditClient(_BaseClient):
|
13
|
+
def __init__(self, client: httpx.Client, retry_count: int, thread_count: int) -> None:
|
14
|
+
self._client = client
|
15
|
+
self._limit = 10_000
|
16
|
+
self._thread_count = thread_count
|
17
|
+
self._url = "https://audit.anaplan.com/audit/api/1/events"
|
18
|
+
super().__init__(retry_count, client)
|
19
|
+
|
20
|
+
def _get_total(self, days_into_past: int = 60, event_type: Event = "all") -> int:
|
21
|
+
return ( # noqa
|
22
|
+
self._get(
|
23
|
+
self._url,
|
24
|
+
params={
|
25
|
+
"limit": 0,
|
26
|
+
"type": event_type,
|
27
|
+
"intervalInHours": days_into_past * 24,
|
28
|
+
},
|
29
|
+
)
|
30
|
+
)["meta"]["paging"]["totalSize"] # noqa
|
31
|
+
|
32
|
+
def _get_result_page(
|
33
|
+
self,
|
34
|
+
days_into_past: int = 60,
|
35
|
+
event_type: Event = "all",
|
36
|
+
limit: int = 10_000,
|
37
|
+
offset: int = 0,
|
38
|
+
) -> list:
|
39
|
+
return (
|
40
|
+
self._get(
|
41
|
+
self._url,
|
42
|
+
params={
|
43
|
+
"intervalInHours": days_into_past * 24,
|
44
|
+
"limit": limit,
|
45
|
+
"offset": offset,
|
46
|
+
"type": event_type,
|
47
|
+
},
|
48
|
+
)
|
49
|
+
).get("response", [])
|
50
|
+
|
51
|
+
def get_events(self, days_into_past: int = 30, event_type: Event = "all") -> list:
|
52
|
+
total = self._get_total(days_into_past, event_type)
|
53
|
+
if total == 0:
|
54
|
+
return []
|
55
|
+
if total <= 10_000:
|
56
|
+
return self._get_result_page(total)
|
57
|
+
|
58
|
+
from concurrent.futures import ThreadPoolExecutor
|
59
|
+
|
60
|
+
with ThreadPoolExecutor(max_workers=self._thread_count) as executor:
|
61
|
+
futures = [
|
62
|
+
executor.submit(
|
63
|
+
self._get_result_page, days_into_past, event_type, self._limit, n * self._limit
|
64
|
+
)
|
65
|
+
for n in range(ceil(total / self._limit))
|
66
|
+
]
|
67
|
+
results = [future.result() for future in futures]
|
68
|
+
return list(chain.from_iterable(results))
|
@@ -18,6 +18,7 @@ from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierExceptio
|
|
18
18
|
from anaplan_sdk.models import Action, Export, File, Import, Model, Process, Workspace
|
19
19
|
|
20
20
|
from ._alm import _AlmClient
|
21
|
+
from ._audit import _AuditClient
|
21
22
|
from ._transactional import _TransactionalClient
|
22
23
|
|
23
24
|
logging.getLogger("httpx").setLevel(logging.CRITICAL)
|
@@ -115,6 +116,7 @@ class Client(_BaseClient):
|
|
115
116
|
_AlmClient(self._client, model_id, self._retry_count) if model_id else None
|
116
117
|
)
|
117
118
|
self._thread_count = multiprocessing.cpu_count()
|
119
|
+
self.audit = _AuditClient(self._client, self._retry_count, self._thread_count)
|
118
120
|
self.status_poll_delay = status_poll_delay
|
119
121
|
self.upload_parallel = upload_parallel
|
120
122
|
self.upload_chunk_size = upload_chunk_size
|
@@ -380,6 +382,14 @@ class Client(_BaseClient):
|
|
380
382
|
self.run_action(action_id)
|
381
383
|
return self.get_file(action_id)
|
382
384
|
|
385
|
+
def list_task_status(self, action_id: int) -> list:
|
386
|
+
"""
|
387
|
+
Retrieves the status of all tasks spawned by the specified action.
|
388
|
+
:param action_id: The identifier of the action that was invoked.
|
389
|
+
:return: The list of tasks spawned by the action.
|
390
|
+
"""
|
391
|
+
return self._get(f"{self._url}/{action_url(action_id)}/{action_id}/tasks").get("tasks", [])
|
392
|
+
|
383
393
|
def get_task_status(
|
384
394
|
self, action_id: int, task_id: str
|
385
395
|
) -> dict[str, float | int | str | list | dict | bool]:
|
@@ -1,19 +1,18 @@
|
|
1
|
-
This section tries to explain Anaplan specific concepts and design choices to Developers
|
2
|
-
|
3
|
-
|
1
|
+
This section tries to explain Anaplan specific concepts and design choices to Developers. It is less interesting for
|
2
|
+
people already familiar with Anaplan. Most of this page applies to the Bulk API, but some concepts are also
|
3
|
+
applicable to the Transactional API. To understand how Anaplan handles data, you are going to have to wrap your head
|
4
|
+
around some fundamental concepts, as well as gain a basic understanding of some Anaplan specific terminology.
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
dataflows and structures are the way they are, obtaining a well-founded understanding of how Anaplan handles data, is
|
8
|
-
essential.
|
9
|
-
|
10
|
-
This page list tries to condense the fundamentals of using the Anaplan Bulk API. The transactional API is different
|
11
|
-
in many ways.
|
12
|
-
|
13
|
-
|
14
|
-
<p align="center" style="margin: 40px 0 40px 0;">
|
6
|
+
The basic high-level view of any Anaplan Integration looks like this:
|
7
|
+
<p align="center" style="margin: 20px 0 40px 0;">
|
15
8
|
<img src="../img/anaplan-overview.webp" alt='Anaplan high-level view' style="border-radius: 15px">
|
16
9
|
</p>
|
10
|
+
!!! tip "TLDR"
|
11
|
+
You upload contents to files. Import Actions import the content from these files into Lists and Modules. Export
|
12
|
+
Actions export the content of Lists and Modules to files. You can download these files. Processes are just sequences
|
13
|
+
of actions. Anything that references the same file must not be run concurrently.
|
14
|
+
|
15
|
+
|
17
16
|
|
18
17
|
## Basic Concepts
|
19
18
|
|
@@ -26,7 +25,7 @@ in many ways.
|
|
26
25
|
- Exports - 116000000000 IDs.
|
27
26
|
- Processes - 118000000000 IDs.
|
28
27
|
- Other Actions - 117000000000 IDs.
|
29
|
-
|
28
|
+
|
30
29
|
- Imports read data from a file and load it into a module. Exports conversely load data from a module to a file. The
|
31
30
|
file id of the resulting file is identical to the export id. "Other Actions" move things around in Anaplan and can
|
32
31
|
also delete data etc. Processes are simply a sequence of the other three.
|
@@ -1,5 +1,5 @@
|
|
1
1
|
!!! note
|
2
2
|
This Class is not meant to be instantiated directly, but rather accessed through the `alm` Property on an
|
3
|
-
instance of [Client](client.md). For more details, see the [Guide](alm.md).
|
3
|
+
instance of [Client](client.md). For more details, see the [Guide](../guides/alm.md).
|
4
4
|
|
5
5
|
::: anaplan_sdk._clients._AlmClient
|
@@ -1,5 +1,5 @@
|
|
1
1
|
!!! note
|
2
2
|
This Class is not meant to be instantiated directly, but rather accessed through the `alm` Property on an
|
3
|
-
instance of [Client](client.md). For more details, see the [Guide](alm.md).
|
3
|
+
instance of [Client](client.md). For more details, see the [Guide](../guides/alm.md).
|
4
4
|
|
5
5
|
::: anaplan_sdk._async_clients._AsyncAlmClient
|
@@ -1,5 +1,5 @@
|
|
1
1
|
!!! note
|
2
2
|
This Class is not meant to be instantiated directly, but rather accessed through the `alm` Property on an
|
3
|
-
instance of [AsyncClient](async_client.md). For more details, see the [Guide](transactional.md).
|
3
|
+
instance of [AsyncClient](async_client.md). For more details, see the [Guide](../guides/transactional.md).
|
4
4
|
|
5
5
|
::: anaplan_sdk._async_clients._alm._AsyncAlmClient
|
@@ -1,5 +1,5 @@
|
|
1
1
|
!!! note
|
2
2
|
This Class is not meant to be instantiated directly, but rather accessed through the `transactional` Property on an
|
3
|
-
instance of [Client](client.md). For more details, see the [Guide](transactional.md).
|
3
|
+
instance of [Client](client.md). For more details, see the [Guide](../guides/transactional.md).
|
4
4
|
|
5
5
|
::: anaplan_sdk._clients._TransactionalClient
|
@@ -1,5 +1,3 @@
|
|
1
|
-
## Intro
|
2
|
-
|
3
1
|
The purpose of the Application Lifecycle Management (ALM) API is to make model change management more scalable,
|
4
2
|
automatable, and integrate with other systems.
|
5
3
|
|
@@ -80,5 +78,5 @@ revisions, syncs = await asyncio.gather(alm.get_revisions(), alm.get_sync_tasks(
|
|
80
78
|
///
|
81
79
|
|
82
80
|
!!! note
|
83
|
-
While you can instantiate a [Client](client.md) without the workspace or model parameters, trying to access
|
84
|
-
the [Transactional Client](transactional_client.md) on an instance without the `model_id` will raise a `ValueError`.
|
81
|
+
While you can instantiate a [Client](../api/client.md) without the workspace or model parameters, trying to access
|
82
|
+
the [Transactional Client](../api/transactional_client.md) on an instance without the `model_id` will raise a `ValueError`.
|
@@ -0,0 +1,42 @@
|
|
1
|
+
You can use the Audit API to get fine-grained information about the changes made to any model, usage, user sign-ins,
|
2
|
+
most frequently visited pages and much more. The Audit API exposes most the logs collected by Anaplan.
|
3
|
+
|
4
|
+
For details refer to
|
5
|
+
[the Documentation](https://auditservice.docs.apiary.io/#).
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
The method for the Audit API reside in a different namespace for better API navigability and
|
10
|
+
comprehensiveness, but are accessible through the same client for convenience. For e.g., you can call
|
11
|
+
the `.get_events()` method like so:
|
12
|
+
|
13
|
+
/// tab | Synchronous
|
14
|
+
|
15
|
+
```python
|
16
|
+
import anaplan_sdk
|
17
|
+
|
18
|
+
anaplan = anaplan_sdk.Client(
|
19
|
+
workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
20
|
+
model_id="11111111111111111111111111111111",
|
21
|
+
certificate="~/certs/anaplan.pem",
|
22
|
+
private_key="~/keys/anaplan.pem",
|
23
|
+
)
|
24
|
+
events = anaplan.audit.get_events()
|
25
|
+
```
|
26
|
+
|
27
|
+
///
|
28
|
+
/// tab | Asynchronous
|
29
|
+
|
30
|
+
```python
|
31
|
+
import anaplan_sdk
|
32
|
+
|
33
|
+
anaplan = anaplan_sdk.AsyncClient(
|
34
|
+
workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
35
|
+
model_id="11111111111111111111111111111111",
|
36
|
+
certificate="~/certs/anaplan.pem",
|
37
|
+
private_key="~/keys/anaplan.pem",
|
38
|
+
)
|
39
|
+
events = await anaplan.audit.get_events()
|
40
|
+
```
|
41
|
+
|
42
|
+
///
|
@@ -1,106 +1,172 @@
|
|
1
|
-
!!! tip "Client Settings"
|
2
|
-
Anaplan SDK comes with a set of default options that are efficient for most use cases and general purpose. Mainly, it
|
3
|
-
will compress all data before uploading and leverage Concurrency to speed up- and downloads. with a chunk size of 25MB.
|
4
|
-
However, you can tune the client to better suit your needs. For more information,
|
5
|
-
see [Client Parameters](client.md#anaplan_sdk.Client.__init__).
|
6
|
-
|
7
|
-
## Intro
|
8
|
-
|
9
1
|
When using this SDK you would never know it, but the workflow of performing an import of data into or export from
|
10
2
|
Anaplan is actually quite involved. To give you the full context and allow you to make informed choices, let's take a
|
11
3
|
look at the individual steps, how to perform them individually and how one can put these together to use the Bulk API to
|
12
4
|
the greatest efficiency.
|
13
5
|
|
14
|
-
|
6
|
+
!!! tip "Client Settings"
|
7
|
+
Anaplan SDK comes with a set of default options that are efficient for most use cases and general purpose. Mainly, it
|
8
|
+
will compress all data before uploading and leverage Concurrency to speed up- and downloads, with a chunk size of 25MB.
|
9
|
+
However, you can configure the client to better fit your needs. For more information,
|
10
|
+
see [Client Parameters](../api/client.md#anaplan_sdk.Client.__init__).
|
11
|
+
|
12
|
+
## Basic Usage
|
15
13
|
|
16
|
-
|
14
|
+
### Instantiate a Client
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
Clients are instantiated with the workspace, model and authentication information. There are two primary means of
|
17
|
+
Authentication.
|
18
|
+
|
19
|
+
#### Basic Authentication
|
20
|
+
|
21
|
+
Basic Authentication is unsuitable for Production. Anaplan password policies force password changes every 30, 60 or 90
|
22
|
+
days, depending on tenant settings, making this approach annoying to maintain and error-prone and is thus not
|
23
|
+
recommended for production.
|
24
|
+
|
25
|
+
/// tab | Synchronous
|
26
|
+
|
27
|
+
```python
|
28
|
+
import anaplan_sdk
|
29
|
+
|
30
|
+
anaplan = anaplan_sdk.Client(
|
31
|
+
workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
32
|
+
model_id="11111111111111111111111111111111",
|
33
|
+
user_email="admin@company.com",
|
34
|
+
password="my_super_secret_password",
|
35
|
+
)
|
36
|
+
```
|
26
37
|
|
27
38
|
///
|
28
39
|
|
29
|
-
/// tab |
|
40
|
+
/// tab | Asynchronous
|
41
|
+
|
42
|
+
```python
|
43
|
+
import anaplan_sdk
|
30
44
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
45
|
+
anaplan = anaplan_sdk.AsyncClient(
|
46
|
+
workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
47
|
+
model_id="11111111111111111111111111111111",
|
48
|
+
user_email="admin@company.com",
|
49
|
+
password="my_super_secret_password",
|
50
|
+
)
|
51
|
+
```
|
36
52
|
|
37
53
|
///
|
38
54
|
|
39
|
-
|
55
|
+
#### Certificate Authentication
|
40
56
|
|
41
57
|
/// tab | Synchronous
|
42
58
|
|
43
59
|
```python
|
60
|
+
import anaplan_sdk
|
61
|
+
|
44
62
|
anaplan = anaplan_sdk.Client(
|
45
63
|
workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
46
64
|
model_id="11111111111111111111111111111111",
|
47
65
|
certificate="~/certs/anaplan.pem",
|
48
66
|
private_key="~/keys/anaplan.pem",
|
67
|
+
private_key_password="my_super_secret_password",
|
49
68
|
)
|
50
|
-
|
51
|
-
export_content = anaplan.export_and_download(116000000000)
|
69
|
+
|
52
70
|
```
|
53
71
|
|
54
72
|
///
|
55
73
|
/// tab | Asynchronous
|
56
74
|
|
57
75
|
```python
|
76
|
+
import anaplan_sdk
|
77
|
+
|
58
78
|
anaplan = anaplan_sdk.AsyncClient(
|
59
79
|
workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
60
80
|
model_id="11111111111111111111111111111111",
|
61
81
|
certificate="~/certs/anaplan.pem",
|
62
82
|
private_key="~/keys/anaplan.pem",
|
83
|
+
private_key_password="my_super_secret_password",
|
63
84
|
)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
85
|
+
|
86
|
+
```
|
87
|
+
|
88
|
+
///
|
89
|
+
|
90
|
+
### Importing data
|
91
|
+
|
92
|
+
/// tab | Synchronous
|
93
|
+
|
94
|
+
```python
|
95
|
+
anaplan.upload_file(113000000000, b"Hello Anaplan")
|
96
|
+
anaplan.run_action(112000000000)
|
97
|
+
|
98
|
+
# Or in short:
|
99
|
+
anaplan.upload_and_import(113000000000, b"Hello Anaplan", 112000000000)
|
68
100
|
```
|
69
101
|
|
70
|
-
|
71
|
-
|
102
|
+
///
|
103
|
+
/// tab | Asynchronous
|
104
|
+
|
105
|
+
```python
|
106
|
+
await anaplan.upload_file(113000000000, b"Hello Anaplan")
|
107
|
+
await anaplan.run_action(112000000000)
|
108
|
+
|
109
|
+
# Or in short:
|
110
|
+
await anaplan.upload_and_import(113000000000, b"Hello Anaplan", 112000000000)
|
111
|
+
|
112
|
+
```
|
72
113
|
|
73
114
|
///
|
74
115
|
|
75
|
-
|
76
|
-
|
116
|
+
### Exporting data
|
117
|
+
|
118
|
+
/// tab | Synchronous
|
119
|
+
|
120
|
+
```python
|
121
|
+
anaplan.run_action(116000000000)
|
122
|
+
content = anaplan.get_file(116000000000)
|
123
|
+
|
124
|
+
# Or in short:
|
125
|
+
content = anaplan.export_and_download(116000000000)
|
126
|
+
```
|
127
|
+
|
128
|
+
///
|
129
|
+
/// tab | Asynchronous
|
130
|
+
|
131
|
+
```python
|
132
|
+
await anaplan.run_action(116000000000)
|
133
|
+
content = await anaplan.get_file(116000000000)
|
134
|
+
|
135
|
+
# Or in short:
|
136
|
+
content = await anaplan.export_and_download(116000000000)
|
137
|
+
```
|
138
|
+
|
139
|
+
///
|
77
140
|
|
78
141
|
## Applications
|
79
142
|
|
80
143
|
### One source with multiple Actions
|
81
144
|
|
82
|
-
One of the most common patterns you'll find working with Anaplan is
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
those.
|
145
|
+
One of the most common patterns you'll find working with Anaplan is:
|
146
|
+
|
147
|
+
1. Upload content
|
148
|
+
2. Import into a list
|
149
|
+
3. Import into a module
|
88
150
|
|
89
|
-
The
|
90
|
-
into the list and the other one importing into the module and then
|
91
|
-
like this:
|
151
|
+
The recommended way to do this is to have your model builder create two actions that reference the same file, one
|
152
|
+
importing into the list and the other one importing into the module and then wrap them into a process. This would again
|
153
|
+
just look like this:
|
92
154
|
|
93
155
|
/// tab | Synchronous
|
94
156
|
|
95
157
|
```python
|
96
|
-
anaplan.upload_and_import(
|
158
|
+
anaplan.upload_and_import(
|
159
|
+
file_id=113000000000, content=b"Hello World!", action_id=118000000000
|
160
|
+
)
|
97
161
|
```
|
98
162
|
|
99
163
|
///
|
100
164
|
/// tab | Asynchronous
|
101
165
|
|
102
166
|
```python
|
103
|
-
await anaplan.upload_and_import(
|
167
|
+
await anaplan.upload_and_import(
|
168
|
+
file_id=113000000000, content=b"Hello World!", action_id=118000000000
|
169
|
+
)
|
104
170
|
```
|
105
171
|
|
106
172
|
///
|
@@ -159,7 +225,7 @@ await anaplan.run_action(118000000000)
|
|
159
225
|
If you have a file that is larger than your available RAM, or you are consuming chunks from i.e. a queue until it is
|
160
226
|
exhausted and thus cannot know the number of expected chunks ahead of time, you can use the `upload_file_stream` method.
|
161
227
|
You can pass an Iterator - in this case a Generator - that yields the chunks to this method, and it will handle the
|
162
|
-
rest. The `upload_file_stream` method on the [AsyncClient](async_client.md#anaplan_sdk.AsyncClient.upload_file_stream)
|
228
|
+
rest. The `upload_file_stream` method on the [AsyncClient](../api/async_client.md#anaplan_sdk.AsyncClient.upload_file_stream)
|
163
229
|
accepts both `AsyncIterator[bytes | str]` and `Iterator[str | bytes]`.
|
164
230
|
|
165
231
|
This will work nicely with i.e. [`scan_parquet()`](https://docs.pola.rs/user-guide/io/parquet/#scan)
|
@@ -205,14 +271,13 @@ This will allow you to upload files of arbitrary size without running into memor
|
|
205
271
|
small enough to fit into memory. It will work equally well with any other source that can be read in chunks and
|
206
272
|
especially well with sources that can be read lazily or return the results sets in chunks by default.
|
207
273
|
|
208
|
-
|
209
274
|
You can in the same way use the `get_file_stream` method to download files in chunks.
|
210
275
|
|
211
276
|
/// tab | Synchronous
|
212
277
|
|
213
278
|
```python
|
214
279
|
for chunk in anaplan.get_file_stream(113000000040):
|
215
|
-
|
280
|
+
... # do something with the chunk
|
216
281
|
```
|
217
282
|
|
218
283
|
///
|