anaplan-sdk 0.2.5__py3-none-any.whl → 0.2.7__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,5 +1,6 @@
1
1
  from ._alm import _AsyncAlmClient
2
+ from ._audit import _AsyncAuditClient
2
3
  from ._bulk import AsyncClient
3
4
  from ._transactional import _AsyncTransactionalClient
4
5
 
5
- __all__ = ["_AsyncAlmClient", "AsyncClient", "_AsyncTransactionalClient"]
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]:
anaplan_sdk/_base.py CHANGED
@@ -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
  """
@@ -1,5 +1,6 @@
1
1
  from ._alm import _AlmClient
2
+ from ._audit import _AuditClient
2
3
  from ._bulk import Client
3
4
  from ._transactional import _TransactionalClient
4
5
 
5
- __all__ = ["_AlmClient", "Client", "_TransactionalClient"]
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,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,19 @@
1
+ anaplan_sdk/__init__.py,sha256=5fr-SZSsH6f3vkRUTDoK6xdAN31cCpe9Mwz2VNu47Uw,134
2
+ anaplan_sdk/_auth.py,sha256=wRcMpdDiHuV-dtiGAKElDiwzfZAEeTOFWfSfaLwNPoU,6597
3
+ anaplan_sdk/_base.py,sha256=2Te7rg_o8_1KD64NfKsDiPLladaoDxMuzk0PaAUNSr8,5299
4
+ anaplan_sdk/exceptions.py,sha256=ALkA9fBF0NQ7dufFxV6AivjmHyuJk9DOQ9jtJV2n7f0,1809
5
+ anaplan_sdk/models.py,sha256=ceMaVctpjwQHk7a71Io_-1YcCQshx5i1YYnqxS51nYw,12491
6
+ anaplan_sdk/_async_clients/__init__.py,sha256=wT6qfi4f_4vLFWTJQTsBw8r3DrHtoTIVqi88p5_j-Cg,259
7
+ anaplan_sdk/_async_clients/_alm.py,sha256=-sFk91tRihc5GVPlW41-I5sQ0fxSRCSYTop5S4q2lHc,3673
8
+ anaplan_sdk/_async_clients/_audit.py,sha256=sPu5D_4ENh0iFdlPjuOA1ffFUdAhs2T_M5vHzpwFouA,2194
9
+ anaplan_sdk/_async_clients/_bulk.py,sha256=JmjvZKuekHQceBmItexhkk9YT6njg5_gsYLdtGCyWzE,22202
10
+ anaplan_sdk/_async_clients/_transactional.py,sha256=wX_1U5YS4uwrr8D8MfNkfeA-ylFERMb-6xvewG799xY,4961
11
+ anaplan_sdk/_clients/__init__.py,sha256=FsbwvZC1FHrxuRXwbPxUzbhz_lO1DpXIxEOjx6-3QuA,219
12
+ anaplan_sdk/_clients/_alm.py,sha256=Vcrn3TjHlH365O7F1TKc1WiZWURNqQf_gSc8UUVESM8,3564
13
+ anaplan_sdk/_clients/_audit.py,sha256=vYJxZPM-OS5f4wgzKd2Wcl9kbeypeYrlhT7JSh89KM0,2269
14
+ anaplan_sdk/_clients/_bulk.py,sha256=_rhp7MgcIRq0FYwxEzvU7jBkDm5PQaw9YCU_zqIwauY,22222
15
+ anaplan_sdk/_clients/_transactional.py,sha256=4NYhq2HdxNh4K-HqMyYoyAEqKih2eTtFiXDZi8xOaf8,4695
16
+ anaplan_sdk-0.2.7.dist-info/METADATA,sha256=Kx0L4iI7OVMVSEHTZpv-uCwe8fTGBkOORKNuH-RUSFw,3599
17
+ anaplan_sdk-0.2.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ anaplan_sdk-0.2.7.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
19
+ anaplan_sdk-0.2.7.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- anaplan_sdk/__init__.py,sha256=5fr-SZSsH6f3vkRUTDoK6xdAN31cCpe9Mwz2VNu47Uw,134
2
- anaplan_sdk/_auth.py,sha256=wRcMpdDiHuV-dtiGAKElDiwzfZAEeTOFWfSfaLwNPoU,6597
3
- anaplan_sdk/_base.py,sha256=qO5NVmvTYvH7SIEe6SlA-pdMiCEe61EgWXxifvOSG7o,5061
4
- anaplan_sdk/exceptions.py,sha256=ALkA9fBF0NQ7dufFxV6AivjmHyuJk9DOQ9jtJV2n7f0,1809
5
- anaplan_sdk/models.py,sha256=ceMaVctpjwQHk7a71Io_-1YcCQshx5i1YYnqxS51nYw,12491
6
- anaplan_sdk/_async_clients/__init__.py,sha256=OOQrL69Xa-F1Mx3N_UaipdbtwNDMbZ6rJB-wHo54Ys4,199
7
- anaplan_sdk/_async_clients/_alm.py,sha256=-sFk91tRihc5GVPlW41-I5sQ0fxSRCSYTop5S4q2lHc,3673
8
- anaplan_sdk/_async_clients/_bulk.py,sha256=feCUZKh4PfOoC1VNIlBY5DBCL3MxuOsPrDsUPL56dVw,21662
9
- anaplan_sdk/_async_clients/_transactional.py,sha256=wX_1U5YS4uwrr8D8MfNkfeA-ylFERMb-6xvewG799xY,4961
10
- anaplan_sdk/_clients/__init__.py,sha256=vKMLbsWDjlEiJY1q296lnZzYkJk3eKtIwJLoWrO7yxk,169
11
- anaplan_sdk/_clients/_alm.py,sha256=Vcrn3TjHlH365O7F1TKc1WiZWURNqQf_gSc8UUVESM8,3564
12
- anaplan_sdk/_clients/_bulk.py,sha256=sdOQeJbxxJTPNKmUN3JMiKBVXTR8HJJcsFaKjduiHq4,21705
13
- anaplan_sdk/_clients/_transactional.py,sha256=4NYhq2HdxNh4K-HqMyYoyAEqKih2eTtFiXDZi8xOaf8,4695
14
- anaplan_sdk-0.2.5.dist-info/METADATA,sha256=BdoPh7HxuwpO1F3dFMqsH1mLu_JHSYkz3HqYeNfVfJo,3599
15
- anaplan_sdk-0.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- anaplan_sdk-0.2.5.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
17
- anaplan_sdk-0.2.5.dist-info/RECORD,,