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.
Files changed (63) hide show
  1. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/PKG-INFO +1 -1
  2. anaplan_sdk-0.2.7/anaplan_sdk/_async_clients/__init__.py +6 -0
  3. anaplan_sdk-0.2.7/anaplan_sdk/_async_clients/_audit.py +69 -0
  4. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_async_clients/_bulk.py +13 -1
  5. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_base.py +8 -4
  6. anaplan_sdk-0.2.7/anaplan_sdk/_clients/__init__.py +6 -0
  7. anaplan_sdk-0.2.7/anaplan_sdk/_clients/_audit.py +68 -0
  8. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_clients/_bulk.py +10 -0
  9. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/docs/anaplan_explained.md +13 -14
  10. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/alm_client.md +1 -1
  11. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/async_alm_client.md +1 -1
  12. anaplan_sdk-0.2.7/docs/api/async_audit_client.md +5 -0
  13. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/async_transactional_client.md +1 -1
  14. anaplan_sdk-0.2.7/docs/api/audit_client.md +5 -0
  15. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/transactional_client.md +1 -1
  16. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/alm.md +2 -4
  17. anaplan_sdk-0.2.7/docs/guides/audit.md +42 -0
  18. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/bulk.md +114 -49
  19. anaplan_sdk-0.2.7/docs/guides/transactional.md +242 -0
  20. anaplan_sdk-0.2.7/docs/img/anaplan-overview.webp +0 -0
  21. anaplan_sdk-0.2.7/docs/index.md +31 -0
  22. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/docs/installation.md +10 -3
  23. anaplan_sdk-0.2.7/docs/quickstart.md +102 -0
  24. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/mkdocs.yml +17 -14
  25. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/pyproject.toml +3 -2
  26. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/uv.lock +1100 -1129
  27. anaplan_sdk-0.2.5/anaplan_sdk/_async_clients/__init__.py +0 -5
  28. anaplan_sdk-0.2.5/anaplan_sdk/_clients/__init__.py +0 -5
  29. anaplan_sdk-0.2.5/docs/img/anaplan-overview.webp +0 -0
  30. anaplan_sdk-0.2.5/docs/index.md +0 -31
  31. anaplan_sdk-0.2.5/docs/quickstart.md +0 -162
  32. anaplan_sdk-0.2.5/docs/transactional.md +0 -119
  33. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.github/dependabot.yml +0 -0
  34. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.github/workflows/docs.yml +0 -0
  35. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.github/workflows/lint.yml +0 -0
  36. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.github/workflows/tests.yml +0 -0
  37. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.gitignore +0 -0
  38. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/.pre-commit-config.yaml +0 -0
  39. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/LICENSE +0 -0
  40. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/README.md +0 -0
  41. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/__init__.py +0 -0
  42. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_async_clients/_alm.py +0 -0
  43. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_async_clients/_transactional.py +0 -0
  44. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_auth.py +0 -0
  45. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_clients/_alm.py +0 -0
  46. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/_clients/_transactional.py +0 -0
  47. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/exceptions.py +0 -0
  48. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/anaplan_sdk/models.py +0 -0
  49. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/async_client.md +0 -0
  50. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/client.md +0 -0
  51. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/exceptions.md +0 -0
  52. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/api}/models.md +0 -0
  53. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/docs/css/styles.css +0 -0
  54. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/bulk_vs_transactional.md +0 -0
  55. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/logging.md +0 -0
  56. {anaplan_sdk-0.2.5/docs → anaplan_sdk-0.2.7/docs/guides}/multiple_models.md +0 -0
  57. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/docs/img/anaplan-sdk.webp +0 -0
  58. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_alm_client.py +0 -0
  59. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_async_alm_client.py +0 -0
  60. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_async_client.py +0 -0
  61. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_async_transactional_client.py +0 -0
  62. {anaplan_sdk-0.2.5 → anaplan_sdk-0.2.7}/tests/test_client.py +0 -0
  63. {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.5
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,6 @@
1
+ from ._alm import _AsyncAlmClient
2
+ from ._audit import _AsyncAuditClient
3
+ from ._bulk import AsyncClient
4
+ from ._transactional import _AsyncTransactionalClient
5
+
6
+ __all__ = ["AsyncClient", "_AsyncAlmClient", "_AsyncAuditClient", "_AsyncTransactionalClient"]
@@ -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()` a nd `get_file()` to run an export action and
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,6 @@
1
+ from ._alm import _AlmClient
2
+ from ._audit import _AuditClient
3
+ from ._bulk import Client
4
+ from ._transactional import _TransactionalClient
5
+
6
+ __all__ = ["Client", "_AlmClient", "_AuditClient", "_TransactionalClient"]
@@ -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 to enable a better
2
- understanding of the API. It is less interesting for people
3
- already familiar with Anaplan.
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
- To understand how Anaplan handles data, you are going to have to wrap your head around some fundamental concepts, as
6
- well as gain a basic understanding of some Anaplan specific terminology. To truly understand the Anaplan API and why the
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
@@ -0,0 +1,5 @@
1
+ !!! note
2
+ This Class is not meant to be instantiated directly, but rather accessed through the `audit` Property on an
3
+ instance of [Client](client.md). For more details, see the [Guide](../guides/audit.md).
4
+
5
+ ::: anaplan_sdk._async_clients._audit._AuditClient
@@ -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
@@ -0,0 +1,5 @@
1
+ !!! note
2
+ This Class is not meant to be instantiated directly, but rather accessed through the `audit` Property on an
3
+ instance of [Client](client.md). For more details, see the [Guide](../guides/audit.md).
4
+
5
+ ::: anaplan_sdk._clients._audit._AuditClient
@@ -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
- Assuming you already know all the relevant Id's, the steps are:
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
- /// tab | Import
14
+ ### Instantiate a Client
17
15
 
18
- 1. Chunk your content. There is no enforced hard limit on chunk sizes, but there is a strong Recommendation to not
19
- exceed 50 MB and in practice you would be seeking to keep them smaller still. This SDK's default chunk size is 25 MB.
20
- 2. Set the chunk count, if you don't know this number ahead of time, set it to -1.
21
- 3. Upload all chunks
22
- 4. Mark the upload as complete. Only necessary if you set the count to -1 in step 2.
23
- 5. Trigger the import action. This will return a Task Id for the task you just spawned.
24
- 6. Poll the task status until the tasks completes.
25
- 7. Validate the task outcome.
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 | Export
40
+ /// tab | Asynchronous
41
+
42
+ ```python
43
+ import anaplan_sdk
30
44
 
31
- 1. Run the export.
32
- 2. Poll the task status until the tasks completes.
33
- 3. Get the file info and retrieve the chunk count.
34
- 4. Download all chunks.
35
- 5. Merge the chunks.
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
- With this SDK, all of the above is condensed to:
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
- anaplan.upload_and_import(113000000000, b"Hello World!", 118000000000)
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
- _, export_content = await gather(
65
- anaplan.upload_and_import(113000000000, b"Hello World!", 118000000000),
66
- anaplan.export_and_download(116000000000),
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
- Here we can safely run the import and export concurrently, as they have no overlap in the files the reference. If they
71
- did, this would be a terrible idea. See [this Chapter](bulk_vs_transactional.md/#the-bad) for details on this.
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
- This standard case will typically cover most of your needs. Let's now look at some more involved examples, where you
76
- may need some more control over the underlying files and actions to achieve a more efficient exchange.
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 the *upload content -> import into list -> import
83
- into module*. As a hand-wavy TL;DR, you can think of Lists as collections of Metadata and Modules as Tables holding the
84
- actual data. Importing into a list, you must provide a unique identifier, which will be stored in the list alongside
85
- some additional information about this record you can add, and the module will then hold all the records for each id.
86
- Since uploading the content the module and the list share twice would be redundant and inefficient, we want to group
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 easiest way to do this is to have your model builder create two actions that reference the same file, one importing
90
- into the list and the other one importing into the module and then put them into a process. This would again just look
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(113000000000, b"Hello World!", 118000000000)
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(113000000000, b"Hello World!", 118000000000)
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
- ... # do something with the chunk
280
+ ... # do something with the chunk
216
281
  ```
217
282
 
218
283
  ///