anaplan-sdk 0.3.1b1__tar.gz → 0.4.0a1__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.4.0a1/.github/workflows/lint.yml +9 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/.github/workflows/tests.yml +3 -1
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/.pre-commit-config.yaml +2 -2
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/PKG-INFO +33 -2
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/README.md +28 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/_async_clients/__init__.py +13 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_async_clients/_alm.py +0 -1
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_async_clients/_audit.py +9 -1
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_async_clients/_bulk.py +57 -22
- anaplan_sdk-0.4.0a1/anaplan_sdk/_async_clients/_cloud_works.py +344 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/_async_clients/_cw_flow.py +80 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_async_clients/_transactional.py +0 -1
- anaplan_sdk-0.4.0a1/anaplan_sdk/_auth.py +248 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_base.py +116 -27
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_clients/_alm.py +0 -1
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_clients/_audit.py +9 -1
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_clients/_bulk.py +57 -24
- anaplan_sdk-0.4.0a1/anaplan_sdk/_clients/_cloud_works.py +342 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/_clients/_cw_flow.py +78 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_clients/_transactional.py +0 -1
- anaplan_sdk-0.4.0a1/anaplan_sdk/models/__init__.py +49 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/models/_alm.py +55 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/models/_base.py +17 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/models/_bulk.py +176 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/models/_transactional.py +94 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/models/cloud_works.py +478 -0
- anaplan_sdk-0.4.0a1/anaplan_sdk/models/flows.py +86 -0
- {anaplan_sdk-0.3.1b1/docs/api → anaplan_sdk-0.4.0a1/docs/api/async}/async_alm_client.md +1 -1
- {anaplan_sdk-0.3.1b1/docs/api → anaplan_sdk-0.4.0a1/docs/api/async}/async_audit_client.md +1 -1
- anaplan_sdk-0.4.0a1/docs/api/async/async_cw_client.md +1 -0
- anaplan_sdk-0.4.0a1/docs/api/async/async_flows_client.md +1 -0
- {anaplan_sdk-0.3.1b1/docs/api → anaplan_sdk-0.4.0a1/docs/api/async}/async_transactional_client.md +1 -1
- anaplan_sdk-0.3.1b1/docs/api/alm_client.md → anaplan_sdk-0.4.0a1/docs/api/sync/sync_alm_client.md +1 -1
- anaplan_sdk-0.3.1b1/docs/api/audit_client.md → anaplan_sdk-0.4.0a1/docs/api/sync/sync_audit_client.md +1 -1
- anaplan_sdk-0.4.0a1/docs/api/sync/sync_cw_client.md +1 -0
- anaplan_sdk-0.4.0a1/docs/api/sync/sync_flows_client.md +1 -0
- anaplan_sdk-0.3.1b1/docs/api/transactional_client.md → anaplan_sdk-0.4.0a1/docs/api/sync/sync_transactional_client.md +1 -1
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/css/styles.css +32 -11
- anaplan_sdk-0.4.0a1/docs/guides/alm.md +71 -0
- anaplan_sdk-0.4.0a1/docs/guides/audit.md +67 -0
- anaplan_sdk-0.4.0a1/docs/guides/bulk.md +276 -0
- anaplan_sdk-0.4.0a1/docs/guides/cloud_works.md +307 -0
- anaplan_sdk-0.4.0a1/docs/guides/index.md +21 -0
- anaplan_sdk-0.4.0a1/docs/guides/multiple_models.md +32 -0
- anaplan_sdk-0.4.0a1/docs/guides/transactional.md +204 -0
- anaplan_sdk-0.4.0a1/docs/img/anaplan-sdk.webp +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/index.md +8 -1
- anaplan_sdk-0.4.0a1/docs/installation.md +59 -0
- anaplan_sdk-0.4.0a1/docs/js/assets/hljs.js +2973 -0
- anaplan_sdk-0.4.0a1/docs/js/assets/hljs.min.js +1 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/js/assets/python.js +9 -9
- anaplan_sdk-0.4.0a1/docs/js/assets/python.min.js +1 -0
- anaplan_sdk-0.4.0a1/docs/js/highlight.js +9 -0
- anaplan_sdk-0.4.0a1/docs/js/highlight.min.js +1 -0
- anaplan_sdk-0.4.0a1/docs/quickstart.md +138 -0
- anaplan_sdk-0.4.0a1/mkdocs.yml +108 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/pyproject.toml +93 -88
- anaplan_sdk-0.4.0a1/tests/async/conftest.py +105 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/tests/async/test_async_audit_client.py +5 -0
- anaplan_sdk-0.4.0a1/tests/async/test_async_cloud_works_client.py +194 -0
- anaplan_sdk-0.4.0a1/tests/async/test_async_flows_client.py +49 -0
- anaplan_sdk-0.4.0a1/tests/conftest.py +279 -0
- anaplan_sdk-0.4.0a1/tests/sync/conftest.py +105 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/tests/sync/test_audit_client.py +5 -0
- anaplan_sdk-0.4.0a1/tests/sync/test_cloud_works_client.py +186 -0
- anaplan_sdk-0.4.0a1/tests/sync/test_flows_client.py +47 -0
- anaplan_sdk-0.4.0a1/uv.lock +1180 -0
- anaplan_sdk-0.3.1b1/.github/workflows/lint.yml +0 -8
- anaplan_sdk-0.3.1b1/anaplan_sdk/_async_clients/__init__.py +0 -6
- anaplan_sdk-0.3.1b1/anaplan_sdk/_auth.py +0 -135
- anaplan_sdk-0.3.1b1/anaplan_sdk/models.py +0 -329
- anaplan_sdk-0.3.1b1/docs/guides/alm.md +0 -82
- anaplan_sdk-0.3.1b1/docs/guides/audit.md +0 -81
- anaplan_sdk-0.3.1b1/docs/guides/bulk.md +0 -340
- anaplan_sdk-0.3.1b1/docs/guides/multiple_models.md +0 -35
- anaplan_sdk-0.3.1b1/docs/guides/transactional.md +0 -242
- anaplan_sdk-0.3.1b1/docs/img/anaplan-sdk.webp +0 -0
- anaplan_sdk-0.3.1b1/docs/installation.md +0 -28
- anaplan_sdk-0.3.1b1/docs/js/assets/hljs.js +0 -1242
- anaplan_sdk-0.3.1b1/docs/js/highlight.js +0 -12
- anaplan_sdk-0.3.1b1/docs/quickstart.md +0 -162
- anaplan_sdk-0.3.1b1/mkdocs.yml +0 -86
- anaplan_sdk-0.3.1b1/tests/async/conftest.py +0 -65
- anaplan_sdk-0.3.1b1/tests/conftest.py +0 -17
- anaplan_sdk-0.3.1b1/tests/sync/conftest.py +0 -65
- anaplan_sdk-0.3.1b1/uv.lock +0 -1152
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/.github/dependabot.yml +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/.github/workflows/docs.yml +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/.gitignore +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/LICENSE +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/__init__.py +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/_clients/__init__.py +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/anaplan_sdk/exceptions.py +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/anaplan_explained.md +0 -0
- {anaplan_sdk-0.3.1b1/docs/api → anaplan_sdk-0.4.0a1/docs/api/async}/async_client.md +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/api/exceptions.md +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/api/models.md +0 -0
- /anaplan_sdk-0.3.1b1/docs/api/client.md → /anaplan_sdk-0.4.0a1/docs/api/sync/sync_client.md +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/guides/bulk_vs_transactional.md +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/guides/logging.md +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/docs/img/anaplan-overview.webp +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/tests/async/test_async_alm_client.py +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/tests/async/test_async_client.py +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/tests/async/test_async_transactional_client.py +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/tests/sync/test_alm_client.py +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/tests/sync/test_client.py +0 -0
- {anaplan_sdk-0.3.1b1 → anaplan_sdk-0.4.0a1}/tests/sync/test_transactional_client.py +0 -0
@@ -13,6 +13,8 @@ jobs:
|
|
13
13
|
ANAPLAN_SDK_TEST_MODEL_ID: ${{secrets.ANAPLAN_SDK_TEST_MODEL_ID}}
|
14
14
|
ANAPLAN_SDK_TEST_CERT: ${{secrets.ANAPLAN_SDK_TEST_CERT}}
|
15
15
|
ANAPLAN_SDK_TEST_PK: ${{secrets.ANAPLAN_SDK_TEST_PK}}
|
16
|
+
AZ_STORAGE_ACCOUNT: ${{secrets.AZ_STORAGE_ACCOUNT}}
|
17
|
+
AZ_STORAGE_SAS_TOKEN: ${{secrets.AZ_STORAGE_SAS_TOKEN}}
|
16
18
|
|
17
19
|
name: "Python ${{ matrix.python-version }}"
|
18
20
|
runs-on: "ubuntu-latest"
|
@@ -32,4 +34,4 @@ jobs:
|
|
32
34
|
pip install uv
|
33
35
|
uv sync
|
34
36
|
- name: "Run tests"
|
35
|
-
run: "uv run python -m pytest -n
|
37
|
+
run: "uv run python -m pytest -n 12 --dist loadfile tests/"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: anaplan-sdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0a1
|
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
|
@@ -10,9 +10,12 @@ License-Expression: Apache-2.0
|
|
10
10
|
License-File: LICENSE
|
11
11
|
Keywords: anaplan,anaplan alm api,anaplan api,anaplan audit api,anaplan bulk api,anaplan integration
|
12
12
|
Requires-Python: >=3.10.4
|
13
|
-
Requires-Dist: cryptography<45.0.0,>=42.0.7
|
14
13
|
Requires-Dist: httpx<1.0.0,>=0.27.0
|
15
14
|
Requires-Dist: pydantic<3.0.0,>=2.7.2
|
15
|
+
Provides-Extra: cert
|
16
|
+
Requires-Dist: cryptography<45.0.0,>=42.0.7; extra == 'cert'
|
17
|
+
Provides-Extra: oauth
|
18
|
+
Requires-Dist: oauthlib<4.0.0,>=3.0.0; extra == 'oauth'
|
16
19
|
Description-Content-Type: text/markdown
|
17
20
|
|
18
21
|
<p align="center" style="margin: 0 0 10px">
|
@@ -44,6 +47,9 @@ providing both synchronous and asynchronous Clients.
|
|
44
47
|
|
45
48
|
Visit [Anaplan SDK](https://vinzenzklass.github.io/anaplan-sdk/) for documentation.
|
46
49
|
|
50
|
+
If you find any issues or feel that this SDK is not adequately covering your use case,
|
51
|
+
please [open an issue](https://github.com/VinzenzKlass/anaplan-sdk/issues/new).
|
52
|
+
|
47
53
|
---
|
48
54
|
|
49
55
|
### Install Anaplan SDK using pip
|
@@ -107,3 +113,28 @@ for model in models:
|
|
107
113
|
|
108
114
|
For more information, API reference and detailed guides,
|
109
115
|
visit [Anaplan SDK](https://vinzenzklass.github.io/anaplan-sdk/).
|
116
|
+
|
117
|
+
### Contributing
|
118
|
+
|
119
|
+
Pull Requests are welcome. For major changes, please open an issue first to discuss what you would like to change. To
|
120
|
+
submit a pull request, please follow the
|
121
|
+
standard [Fork & Pull Request workflow](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork).
|
122
|
+
|
123
|
+
Before submitting your pull request, please ensure that all the files pass linting and formatting checks. You can do
|
124
|
+
this by running the following command:
|
125
|
+
|
126
|
+
```shell
|
127
|
+
uv sync --dev
|
128
|
+
|
129
|
+
ruff check
|
130
|
+
ruff format
|
131
|
+
```
|
132
|
+
|
133
|
+
You can also enable [pre-commit](https://pre-commit.com/) hooks to automatically format and lint your code before
|
134
|
+
committing:
|
135
|
+
|
136
|
+
```shell
|
137
|
+
pre-commit install
|
138
|
+
```
|
139
|
+
|
140
|
+
If your PR goes beyond a simple bug fix or small changes, please add tests to cover your changes.
|
@@ -27,6 +27,9 @@ providing both synchronous and asynchronous Clients.
|
|
27
27
|
|
28
28
|
Visit [Anaplan SDK](https://vinzenzklass.github.io/anaplan-sdk/) for documentation.
|
29
29
|
|
30
|
+
If you find any issues or feel that this SDK is not adequately covering your use case,
|
31
|
+
please [open an issue](https://github.com/VinzenzKlass/anaplan-sdk/issues/new).
|
32
|
+
|
30
33
|
---
|
31
34
|
|
32
35
|
### Install Anaplan SDK using pip
|
@@ -90,3 +93,28 @@ for model in models:
|
|
90
93
|
|
91
94
|
For more information, API reference and detailed guides,
|
92
95
|
visit [Anaplan SDK](https://vinzenzklass.github.io/anaplan-sdk/).
|
96
|
+
|
97
|
+
### Contributing
|
98
|
+
|
99
|
+
Pull Requests are welcome. For major changes, please open an issue first to discuss what you would like to change. To
|
100
|
+
submit a pull request, please follow the
|
101
|
+
standard [Fork & Pull Request workflow](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork).
|
102
|
+
|
103
|
+
Before submitting your pull request, please ensure that all the files pass linting and formatting checks. You can do
|
104
|
+
this by running the following command:
|
105
|
+
|
106
|
+
```shell
|
107
|
+
uv sync --dev
|
108
|
+
|
109
|
+
ruff check
|
110
|
+
ruff format
|
111
|
+
```
|
112
|
+
|
113
|
+
You can also enable [pre-commit](https://pre-commit.com/) hooks to automatically format and lint your code before
|
114
|
+
committing:
|
115
|
+
|
116
|
+
```shell
|
117
|
+
pre-commit install
|
118
|
+
```
|
119
|
+
|
120
|
+
If your PR goes beyond a simple bug fix or small changes, please add tests to cover your changes.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from ._alm import _AsyncAlmClient
|
2
|
+
from ._audit import _AsyncAuditClient
|
3
|
+
from ._bulk import AsyncClient
|
4
|
+
from ._cloud_works import _AsyncCloudWorksClient
|
5
|
+
from ._transactional import _AsyncTransactionalClient
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"AsyncClient",
|
9
|
+
"_AsyncAlmClient",
|
10
|
+
"_AsyncAuditClient",
|
11
|
+
"_AsyncCloudWorksClient",
|
12
|
+
"_AsyncTransactionalClient",
|
13
|
+
]
|
@@ -10,7 +10,6 @@ warnings.filterwarnings("always", category=DeprecationWarning)
|
|
10
10
|
|
11
11
|
class _AsyncAlmClient(_AsyncBaseClient):
|
12
12
|
def __init__(self, client: httpx.AsyncClient, model_id: str, retry_count: int) -> None:
|
13
|
-
self._client = client
|
14
13
|
self._url = f"https://api.anaplan.com/2/0/models/{model_id}/alm"
|
15
14
|
super().__init__(retry_count, client)
|
16
15
|
|
@@ -10,7 +10,6 @@ Event = Literal["all", "byok", "user_activity"]
|
|
10
10
|
|
11
11
|
class _AsyncAuditClient(_AsyncBaseClient):
|
12
12
|
def __init__(self, client: httpx.AsyncClient, retry_count: int) -> None:
|
13
|
-
self._client = client
|
14
13
|
self._limit = 10_000
|
15
14
|
self._url = "https://audit.anaplan.com/audit/api/1/events"
|
16
15
|
super().__init__(retry_count, client)
|
@@ -25,6 +24,15 @@ class _AsyncAuditClient(_AsyncBaseClient):
|
|
25
24
|
for e in await self._get_paginated("https://api.anaplan.com/2/0/users", "users")
|
26
25
|
]
|
27
26
|
|
27
|
+
async def get_user(self, user_id: str = "me") -> User:
|
28
|
+
"""
|
29
|
+
Retrieves information about the specified user, or the authenticated user if none specified.
|
30
|
+
:return: The requested or currently authenticated User.
|
31
|
+
"""
|
32
|
+
return User.model_validate(
|
33
|
+
(await self._get(f"https://api.anaplan.com/2/0/users/{user_id}")).get("user")
|
34
|
+
)
|
35
|
+
|
28
36
|
async def get_events(self, days_into_past: int = 30, event_type: Event = "all") -> list:
|
29
37
|
"""
|
30
38
|
Get audit events from Anaplan Audit API.
|
@@ -1,16 +1,12 @@
|
|
1
|
-
"""
|
2
|
-
Asynchronous Client.
|
3
|
-
"""
|
4
|
-
|
5
1
|
import logging
|
6
2
|
from asyncio import gather, sleep
|
7
3
|
from copy import copy
|
8
|
-
from typing import AsyncIterator, Iterator
|
4
|
+
from typing import AsyncIterator, Callable, Iterator
|
9
5
|
|
10
6
|
import httpx
|
11
7
|
from typing_extensions import Self
|
12
8
|
|
13
|
-
from anaplan_sdk._auth import
|
9
|
+
from anaplan_sdk._auth import create_auth
|
14
10
|
from anaplan_sdk._base import _AsyncBaseClient, action_url
|
15
11
|
from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierException
|
16
12
|
from anaplan_sdk.models import (
|
@@ -27,6 +23,7 @@ from anaplan_sdk.models import (
|
|
27
23
|
|
28
24
|
from ._alm import _AsyncAlmClient
|
29
25
|
from ._audit import _AsyncAuditClient
|
26
|
+
from ._cloud_works import _AsyncCloudWorksClient
|
30
27
|
from ._transactional import _AsyncTransactionalClient
|
31
28
|
|
32
29
|
logging.getLogger("httpx").setLevel(logging.CRITICAL)
|
@@ -54,7 +51,13 @@ class AsyncClient(_AsyncBaseClient):
|
|
54
51
|
certificate: str | bytes | None = None,
|
55
52
|
private_key: str | bytes | None = None,
|
56
53
|
private_key_password: str | bytes | None = None,
|
57
|
-
|
54
|
+
client_id: str | None = None,
|
55
|
+
client_secret: str | None = None,
|
56
|
+
redirect_uri: str | None = None,
|
57
|
+
refresh_token: str | None = None,
|
58
|
+
oauth2_scope: str = "openid profile email offline_access",
|
59
|
+
on_token_refresh: Callable[[dict[str, str]], None] | None = None,
|
60
|
+
timeout: float | httpx.Timeout = 30,
|
58
61
|
retry_count: int = 2,
|
59
62
|
status_poll_delay: int = 1,
|
60
63
|
upload_chunk_size: int = 25_000_000,
|
@@ -82,7 +85,19 @@ class AsyncClient(_AsyncBaseClient):
|
|
82
85
|
itself.
|
83
86
|
:param private_key: The absolute path to the private key file or the private key itself.
|
84
87
|
:param private_key_password: The password to access the private key if there is one.
|
85
|
-
:param
|
88
|
+
:param client_id: The client Id of the Oauth2 Anaplan Client.
|
89
|
+
:param client_secret: The client secret for your Oauth2 Anaplan Client.
|
90
|
+
:param redirect_uri: The redirect URI for your Oauth2 Anaplan Client.
|
91
|
+
:param refresh_token: If you have a valid refresh token, you can pass it to skip the
|
92
|
+
interactive authentication code step.
|
93
|
+
:param oauth2_scope: The scope of the Oauth2 token, if you want to narrow it.
|
94
|
+
:param on_token_refresh: A callback function that is called whenever the token is refreshed.
|
95
|
+
With this you can for example securely store the token in your
|
96
|
+
application or on your server for later reuse. The function
|
97
|
+
must accept a single argument, which is the token dictionary
|
98
|
+
returned by the Oauth2 token endpoint and does not return anything.
|
99
|
+
:param timeout: The timeout in seconds for the HTTP requests. Alternatively, you can pass
|
100
|
+
an instance of `httpx.Timeout` to set the timeout for the HTTP requests.
|
86
101
|
:param retry_count: The number of times to retry an HTTP request if it fails. Set this to 0
|
87
102
|
to never retry. Defaults to 2, meaning each HTTP Operation will be
|
88
103
|
tried a total number of 2 times.
|
@@ -96,34 +111,38 @@ class AsyncClient(_AsyncBaseClient):
|
|
96
111
|
manually assigned so there is typically no value in dynamically
|
97
112
|
creating new files and uploading content to them.
|
98
113
|
"""
|
99
|
-
|
100
|
-
raise ValueError(
|
101
|
-
"Must provide `certificate` and `private_key` or `user_email` and `password`."
|
102
|
-
"If you Private Key is Password protected, must also pass `private_key_password`."
|
103
|
-
)
|
104
|
-
self._client = httpx.AsyncClient(
|
114
|
+
_client = httpx.AsyncClient(
|
105
115
|
auth=(
|
106
|
-
|
107
|
-
|
116
|
+
create_auth(
|
117
|
+
user_email=user_email,
|
118
|
+
password=password,
|
119
|
+
certificate=certificate,
|
120
|
+
private_key=private_key,
|
121
|
+
private_key_password=private_key_password,
|
122
|
+
client_id=client_id,
|
123
|
+
client_secret=client_secret,
|
124
|
+
redirect_uri=redirect_uri,
|
125
|
+
refresh_token=refresh_token,
|
126
|
+
oauth2_scope=oauth2_scope,
|
127
|
+
on_token_refresh=on_token_refresh,
|
108
128
|
)
|
109
|
-
if certificate
|
110
|
-
else AnaplanBasicAuth(user_email=user_email, password=password)
|
111
129
|
),
|
112
130
|
timeout=timeout,
|
113
131
|
)
|
114
132
|
self._retry_count = retry_count
|
115
133
|
self._url = f"https://api.anaplan.com/2/0/workspaces/{workspace_id}/models/{model_id}"
|
116
134
|
self._transactional_client = (
|
117
|
-
_AsyncTransactionalClient(
|
135
|
+
_AsyncTransactionalClient(_client, model_id, retry_count) if model_id else None
|
118
136
|
)
|
119
137
|
self._alm_client = (
|
120
|
-
_AsyncAlmClient(
|
138
|
+
_AsyncAlmClient(_client, model_id, self._retry_count) if model_id else None
|
121
139
|
)
|
122
|
-
self.
|
140
|
+
self._audit = _AsyncAuditClient(_client, self._retry_count)
|
141
|
+
self._cloud_works = _AsyncCloudWorksClient(_client, self._retry_count)
|
123
142
|
self.status_poll_delay = status_poll_delay
|
124
143
|
self.upload_chunk_size = upload_chunk_size
|
125
144
|
self.allow_file_creation = allow_file_creation
|
126
|
-
super().__init__(retry_count,
|
145
|
+
super().__init__(retry_count, _client)
|
127
146
|
|
128
147
|
@classmethod
|
129
148
|
def from_existing(cls, existing: Self, workspace_id: str, model_id: str) -> Self:
|
@@ -145,6 +164,22 @@ class AsyncClient(_AsyncBaseClient):
|
|
145
164
|
client._alm_client = _AsyncAlmClient(existing._client, model_id, existing._retry_count)
|
146
165
|
return client
|
147
166
|
|
167
|
+
@property
|
168
|
+
def audit(self) -> _AsyncAuditClient:
|
169
|
+
"""
|
170
|
+
The Audit Client provides access to the Anaplan Audit API.
|
171
|
+
For details, see https://vinzenzklass.github.io/anaplan-sdk/guides/audit/.
|
172
|
+
"""
|
173
|
+
return self._audit
|
174
|
+
|
175
|
+
@property
|
176
|
+
def cw(self) -> _AsyncCloudWorksClient:
|
177
|
+
"""
|
178
|
+
The Cloud Works Client provides access to the Anaplan Cloud Works API.
|
179
|
+
For details, see https://vinzenzklass.github.io/anaplan-sdk/guides/cloud_works/.
|
180
|
+
"""
|
181
|
+
return self._cloud_works
|
182
|
+
|
148
183
|
@property
|
149
184
|
def transactional(self) -> _AsyncTransactionalClient:
|
150
185
|
"""
|
@@ -0,0 +1,344 @@
|
|
1
|
+
from typing import Any, Literal
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from anaplan_sdk._base import (
|
6
|
+
_AsyncBaseClient,
|
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 _AsyncFlowClient
|
29
|
+
|
30
|
+
|
31
|
+
class _AsyncCloudWorksClient(_AsyncBaseClient):
|
32
|
+
def __init__(self, client: httpx.AsyncClient, retry_count: int) -> None:
|
33
|
+
self._url = "https://api.cloudworks.anaplan.com/2/0/integrations"
|
34
|
+
self._flow = _AsyncFlowClient(client, retry_count)
|
35
|
+
super().__init__(retry_count, client)
|
36
|
+
|
37
|
+
@property
|
38
|
+
def flows(self) -> _AsyncFlowClient:
|
39
|
+
"""
|
40
|
+
Access the Integration Flow APIs.
|
41
|
+
"""
|
42
|
+
return self._flow
|
43
|
+
|
44
|
+
async 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 await self._get_paginated(f"{self._url}/connections", "connections")
|
52
|
+
]
|
53
|
+
|
54
|
+
async 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 = await self._post(
|
63
|
+
f"{self._url}/connections", json=construct_payload(ConnectionInput, con_info)
|
64
|
+
)
|
65
|
+
return res["connections"]["connectionId"]
|
66
|
+
|
67
|
+
async def update_connection(
|
68
|
+
self, con_id: str, con_info: ConnectionBody | dict[str, Any]
|
69
|
+
) -> None:
|
70
|
+
"""
|
71
|
+
Update an existing connection in CloudWorks.
|
72
|
+
:param con_id: The ID of the connection to update.
|
73
|
+
:param con_info: The name and details of the connection. You must pass all the same details
|
74
|
+
as when initially creating the connection again. If you want to update only some of
|
75
|
+
the details, use the `patch_connection` method instead.
|
76
|
+
"""
|
77
|
+
await self._put(f"{self._url}/connections/{con_id}", json=connection_body_payload(con_info))
|
78
|
+
|
79
|
+
async def patch_connection(self, con_id: str, body: dict[str, Any]) -> None:
|
80
|
+
"""
|
81
|
+
Update an existing connection in CloudWorks.
|
82
|
+
:param con_id: The ID of the connection to update.
|
83
|
+
:param body: The name and details of the connection. You can pass all the same details as
|
84
|
+
when initially creating the connection again, or just any one of them.
|
85
|
+
"""
|
86
|
+
await self._patch(f"{self._url}/connections/{con_id}", json=body)
|
87
|
+
|
88
|
+
async def delete_connection(self, con_id: str) -> None:
|
89
|
+
"""
|
90
|
+
Delete an existing connection in CloudWorks.
|
91
|
+
:param con_id: The ID of the connection to delete.
|
92
|
+
"""
|
93
|
+
await self._delete(f"{self._url}/connections/{con_id}")
|
94
|
+
|
95
|
+
async def list_integrations(
|
96
|
+
self, sort_by_name: Literal["ascending", "descending"] = "ascending"
|
97
|
+
) -> list[Integration]:
|
98
|
+
"""
|
99
|
+
List all integrations in CloudWorks.
|
100
|
+
:param sort_by_name: Sort the integrations by name in ascending or descending order.
|
101
|
+
:return: A list of integrations.
|
102
|
+
"""
|
103
|
+
params = {"sortBy": "name" if sort_by_name == "ascending" else "-name"}
|
104
|
+
return [
|
105
|
+
Integration.model_validate(e)
|
106
|
+
for e in await self._get_paginated(f"{self._url}", "integrations", params=params)
|
107
|
+
]
|
108
|
+
|
109
|
+
async def get_integration(self, integration_id: str) -> SingleIntegration:
|
110
|
+
"""
|
111
|
+
Get the details of a specific integration in CloudWorks.
|
112
|
+
|
113
|
+
**Note: This will not include the integration type! While present when listing integrations,
|
114
|
+
the integration type is not included in the details of a single integration.**
|
115
|
+
:param integration_id: The ID of the integration to retrieve.
|
116
|
+
:return: The details of the integration, without the integration type.
|
117
|
+
"""
|
118
|
+
return SingleIntegration.model_validate(
|
119
|
+
(await self._get(f"{self._url}/{integration_id}"))["integration"]
|
120
|
+
)
|
121
|
+
|
122
|
+
async def create_integration(
|
123
|
+
self, body: IntegrationInput | IntegrationProcessInput | dict[str, Any]
|
124
|
+
) -> str:
|
125
|
+
"""
|
126
|
+
Create a new integration in CloudWorks. If not specified, the integration type will be
|
127
|
+
either "Import" or "Export" based on the source and target you provide.
|
128
|
+
|
129
|
+
If you want to instead create a process Integration, you can do so by specifying
|
130
|
+
the `process_id` parameter and passing several jobs. **Be careful to ensure, that all ids
|
131
|
+
specified in the job inputs match what is defined in your model and matches the process.**
|
132
|
+
If this is not the case, this will error, occasionally with a misleading error message,
|
133
|
+
i.e. `XYZ is not defined in your model` even though it is, Anaplan just does not know what
|
134
|
+
to do with it in the location you specified.
|
135
|
+
|
136
|
+
You can also use CloudWorks Integrations to simply schedule a process. To do this, you
|
137
|
+
can simply pass an IntegrationProcessInput instance with the process_id and no jobs. This
|
138
|
+
will create a process integration that will run the process you specified.
|
139
|
+
:param body: The integration information. This can be an
|
140
|
+
IntegrationInput | IntegrationProcessInput instance or a dictionary as per the
|
141
|
+
documentation. If a dictionary is passed, it will be validated against the
|
142
|
+
IntegrationInput model before sending the request.
|
143
|
+
:return: The ID of the new integration.
|
144
|
+
"""
|
145
|
+
json = integration_payload(body)
|
146
|
+
return (await self._post(f"{self._url}", json=json))["integration"]["integrationId"]
|
147
|
+
|
148
|
+
async def update_integration(
|
149
|
+
self, integration_id: str, body: IntegrationInput | IntegrationProcessInput | dict[str, Any]
|
150
|
+
) -> None:
|
151
|
+
"""
|
152
|
+
Update an existing integration in CloudWorks.
|
153
|
+
:param integration_id: The ID of the integration to update.
|
154
|
+
:param body: The name and details of the integration. You must pass all the same details
|
155
|
+
as when initially creating the integration again. If you want to update only some
|
156
|
+
of the details, use the `patch_integration` method instead.
|
157
|
+
"""
|
158
|
+
json = integration_payload(body)
|
159
|
+
await self._put(f"{self._url}/{integration_id}", json=json)
|
160
|
+
|
161
|
+
async def run_integration(self, integration_id: str) -> str:
|
162
|
+
"""
|
163
|
+
Run an integration in CloudWorks.
|
164
|
+
:param integration_id: The ID of the integration to run.
|
165
|
+
:return: The ID of the run instance.
|
166
|
+
"""
|
167
|
+
return (await self._post_empty(f"{self._url}/{integration_id}/run"))["run"]["id"]
|
168
|
+
|
169
|
+
async def delete_integration(self, integration_id: str) -> None:
|
170
|
+
"""
|
171
|
+
Delete an existing integration in CloudWorks.
|
172
|
+
:param integration_id: The ID of the integration to delete.
|
173
|
+
"""
|
174
|
+
await self._delete(f"{self._url}/{integration_id}")
|
175
|
+
|
176
|
+
async def get_run_history(self, integration_id: str) -> list[RunSummary]:
|
177
|
+
"""
|
178
|
+
Get the run history of a specific integration in CloudWorks.
|
179
|
+
:param integration_id: The ID of the integration to retrieve the run history for.
|
180
|
+
:return: A list of run statuses.
|
181
|
+
"""
|
182
|
+
return [
|
183
|
+
RunSummary.model_validate(e)
|
184
|
+
for e in (await self._get(f"{self._url}/runs/{integration_id}"))["history_of_runs"].get(
|
185
|
+
"runs", []
|
186
|
+
)
|
187
|
+
]
|
188
|
+
|
189
|
+
async def get_run_status(self, run_id: str) -> RunStatus:
|
190
|
+
"""
|
191
|
+
Get the status of a specific run in CloudWorks.
|
192
|
+
:param run_id: The ID of the run to retrieve.
|
193
|
+
:return: The details of the run.
|
194
|
+
"""
|
195
|
+
return RunStatus.model_validate((await self._get(f"{self._url}/run/{run_id}"))["run"])
|
196
|
+
|
197
|
+
async def get_run_error(self, run_id: str) -> RunError | None:
|
198
|
+
"""
|
199
|
+
Get the error details of a specific run in CloudWorks. This exposes potential underlying
|
200
|
+
errors like the error of the invoked action, failure dumps and other details.
|
201
|
+
:param run_id: The ID of the run to retrieve.
|
202
|
+
:return: The details of the run error.
|
203
|
+
"""
|
204
|
+
run = await self._get(f"{self._url}/runerror/{run_id}")
|
205
|
+
return RunError.model_validate(run["runs"]) if run.get("runs") else None
|
206
|
+
|
207
|
+
async def create_schedule(
|
208
|
+
self, integration_id: str, schedule: ScheduleInput | dict[str, Any]
|
209
|
+
) -> None:
|
210
|
+
"""
|
211
|
+
Schedule an integration in CloudWorks.
|
212
|
+
:param integration_id: The ID of the integration to schedule.
|
213
|
+
:param schedule: The schedule information. This can be a ScheduleInput instance or a
|
214
|
+
dictionary as per the documentation. If a dictionary is passed, it will be validated
|
215
|
+
against the ScheduleInput model before sending the request.
|
216
|
+
"""
|
217
|
+
await self._post(
|
218
|
+
f"{self._url}/{integration_id}/schedule",
|
219
|
+
json=schedule_payload(integration_id, schedule),
|
220
|
+
)
|
221
|
+
|
222
|
+
async def update_schedule(
|
223
|
+
self, integration_id: str, schedule: ScheduleInput | dict[str, Any]
|
224
|
+
) -> None:
|
225
|
+
"""
|
226
|
+
Update an integration Schedule in CloudWorks. A schedule must already exist.
|
227
|
+
:param integration_id: The ID of the integration to schedule.
|
228
|
+
:param schedule: The schedule information. This can be a ScheduleInput instance or a
|
229
|
+
dictionary as per the documentation. If a dictionary is passed, it will be validated
|
230
|
+
against the ScheduleInput model before sending the request.
|
231
|
+
"""
|
232
|
+
await self._put(
|
233
|
+
f"{self._url}/{integration_id}/schedule",
|
234
|
+
json=schedule_payload(integration_id, schedule),
|
235
|
+
)
|
236
|
+
|
237
|
+
async def set_schedule_status(
|
238
|
+
self, integration_id: str, status: Literal["enabled", "disabled"]
|
239
|
+
) -> None:
|
240
|
+
"""
|
241
|
+
Set the status of an integration schedule in CloudWorks. A schedule must already exist.
|
242
|
+
:param integration_id: The ID of the integration to schedule.
|
243
|
+
:param status: The status of the schedule. This can be either "enabled" or "disabled".
|
244
|
+
"""
|
245
|
+
await self._post_empty(f"{self._url}/{integration_id}/schedule/status/{status}")
|
246
|
+
|
247
|
+
async def delete_schedule(self, integration_id: str) -> None:
|
248
|
+
"""
|
249
|
+
Delete an integration schedule in CloudWorks. A schedule must already exist.
|
250
|
+
:param integration_id: The ID of the integration to schedule.
|
251
|
+
"""
|
252
|
+
await self._delete(f"{self._url}/{integration_id}/schedule")
|
253
|
+
|
254
|
+
async def get_notification_config(
|
255
|
+
self, notification_id: str | None = None, integration_id: str | None = None
|
256
|
+
) -> NotificationConfig:
|
257
|
+
"""
|
258
|
+
Get the notification configuration, either by its Id, or the notification configuration
|
259
|
+
for a specific integration. If the integration_id is specified, the notification_id
|
260
|
+
will be ignored.
|
261
|
+
:param notification_id: The ID of the notification configuration to retrieve.
|
262
|
+
:param integration_id: The ID of the integration to retrieve the notification
|
263
|
+
configuration for.
|
264
|
+
:return: The details of the notification configuration.
|
265
|
+
"""
|
266
|
+
if not (notification_id or integration_id):
|
267
|
+
raise ValueError("Either notification_id or integration_id must be specified.")
|
268
|
+
if integration_id:
|
269
|
+
notification_id = (await self.get_integration(integration_id)).notification_id
|
270
|
+
return NotificationConfig.model_validate(
|
271
|
+
(await self._get(f"{self._url}/notification/{notification_id}"))["notifications"]
|
272
|
+
)
|
273
|
+
|
274
|
+
async def create_notification_config(self, config: NotificationInput | dict[str, Any]) -> str:
|
275
|
+
"""
|
276
|
+
Create a notification configuration for an integration in CloudWorks. This will error if
|
277
|
+
there is already a notification configuration for the integration, which is also the case
|
278
|
+
by default. In this case, you will want to use the `update_notification_config` method
|
279
|
+
instead, to partially update the existing configuration or overwrite it.
|
280
|
+
:param config: The notification configuration. This can be a NotificationInput instance or
|
281
|
+
a dictionary as per the documentation. If a dictionary is passed, it will be
|
282
|
+
validated against the NotificationConfig model before sending the request.
|
283
|
+
:return: The ID of the new notification configuration.
|
284
|
+
"""
|
285
|
+
res = await self._post(
|
286
|
+
f"{self._url}/notification", json=construct_payload(NotificationInput, config)
|
287
|
+
)
|
288
|
+
return res["notification"]["notificationId"]
|
289
|
+
|
290
|
+
async def update_notification_config(
|
291
|
+
self, notification_id: str, config: NotificationInput | dict[str, Any]
|
292
|
+
) -> None:
|
293
|
+
"""
|
294
|
+
Update a notification configuration for an integration in CloudWorks. You cannot pass empty
|
295
|
+
values or nulls to any of the fields If you want to for e.g. override an existing list of
|
296
|
+
users with an empty one, you must delete the notification configuration and create a new
|
297
|
+
one with only the values you want to keep.
|
298
|
+
:param notification_id: The ID of the notification configuration to update.
|
299
|
+
:param config: The notification configuration. This can be a NotificationInput instance or
|
300
|
+
a dictionary as per the documentation. If a dictionary is passed, it will be
|
301
|
+
validated against the NotificationConfig model before sending the request.
|
302
|
+
"""
|
303
|
+
await self._put(
|
304
|
+
f"{self._url}/notification/{notification_id}",
|
305
|
+
json=construct_payload(NotificationInput, config),
|
306
|
+
)
|
307
|
+
|
308
|
+
async def delete_notification_config(
|
309
|
+
self, notification_id: str | None = None, integration_id: str | None = None
|
310
|
+
) -> None:
|
311
|
+
"""
|
312
|
+
Delete a notification configuration for an integration in CloudWorks, either by its Id, or
|
313
|
+
the notification configuration for a specific integration. If the integration_id is
|
314
|
+
specified, the notification_id will be ignored.
|
315
|
+
:param notification_id: The ID of the notification configuration to delete.
|
316
|
+
:param integration_id: The ID of the integration to delete the notification config of.
|
317
|
+
"""
|
318
|
+
if not (notification_id or integration_id):
|
319
|
+
raise ValueError("Either notification_id or integration_id must be specified.")
|
320
|
+
if integration_id:
|
321
|
+
notification_id = (await self.get_integration(integration_id)).notification_id
|
322
|
+
await self._delete(f"{self._url}/notification/{notification_id}")
|
323
|
+
|
324
|
+
async def get_import_error_dump(self, run_id: str) -> bytes:
|
325
|
+
"""
|
326
|
+
Get the error dump of a specific import run in CloudWorks. Calling this for a run_id that
|
327
|
+
did not generate any failure dumps will produce an error.
|
328
|
+
|
329
|
+
**Note that if you need the error dump of an action in a process, you must use the
|
330
|
+
`get_process_error_dump` method instead.**
|
331
|
+
:param run_id: The ID of the run to retrieve.
|
332
|
+
:return: The error dump.
|
333
|
+
"""
|
334
|
+
return await self._get_binary(f"{self._url}/run/{run_id}/dump")
|
335
|
+
|
336
|
+
async def get_process_error_dump(self, run_id: str, action_id: int | str) -> bytes:
|
337
|
+
"""
|
338
|
+
Get the error dump of a specific import run in CloudWorks, that is part of a process.
|
339
|
+
Calling this for a run_id that did not generate any failure dumps will produce an error.
|
340
|
+
:param run_id: The ID of the run to retrieve.
|
341
|
+
:param action_id: The ID of the action to retrieve. This can be found in the RunError.
|
342
|
+
:return: The error dump.
|
343
|
+
"""
|
344
|
+
return await self._get_binary(f"{self._url}/run/{run_id}/process/import/{action_id}/dumps")
|