anaplan-sdk 0.5.0a4__tar.gz → 0.5.0a6__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.5.0a4 → anaplan_sdk-0.5.0a6}/PKG-INFO +1 -1
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_async_clients/_alm.py +2 -1
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_async_clients/_audit.py +5 -2
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_async_clients/_bulk.py +21 -22
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_async_clients/_cloud_works.py +2 -2
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_async_clients/_cw_flow.py +2 -1
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_async_clients/_transactional.py +2 -2
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_clients/_alm.py +2 -1
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_clients/_audit.py +5 -2
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_clients/_bulk.py +21 -22
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_clients/_cloud_works.py +2 -2
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_clients/_cw_flow.py +2 -1
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_clients/_transactional.py +2 -2
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_services.py +20 -193
- anaplan_sdk-0.5.0a6/anaplan_sdk/_utils.py +188 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/models/_bulk.py +6 -4
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/alm.md +2 -6
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/bulk.md +7 -2
- anaplan_sdk-0.5.0a6/docs/guides/multiple_models.md +37 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/pyproject.toml +1 -1
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/async/conftest.py +6 -3
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/async/test_async_client.py +5 -4
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/async/test_async_transactional_client.py +1 -2
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/sync/conftest.py +6 -3
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/sync/test_client.py +6 -5
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/sync/test_transactional_client.py +1 -2
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/uv.lock +1 -1
- anaplan_sdk-0.5.0a4/docs/guides/multiple_models.md +0 -33
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/.github/dependabot.yml +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/.github/workflows/docs.yml +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/.github/workflows/lint.yml +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/.github/workflows/tests.yml +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/.gitignore +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/.pre-commit-config.yaml +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/LICENSE +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/README.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/__init__.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_async_clients/__init__.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_auth.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_clients/__init__.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/_oauth.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/exceptions.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/models/__init__.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/models/_alm.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/models/_base.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/models/_transactional.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/models/cloud_works.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/anaplan_sdk/models/flows.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/async/async_alm_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/async/async_audit_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/async/async_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/async/async_cw_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/async/async_flows_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/async/async_oauth_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/async/async_transactional_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/exceptions.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/models/alm.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/models/bulk.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/models/cloud_works.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/models/flows.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/models/transactional.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/sync/sync_alm_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/sync/sync_audit_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/sync/sync_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/sync/sync_cw_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/sync/sync_flows_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/sync/sync_oauth_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/api/sync/sync_transactional_client.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/assets/overview.html +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/concepts.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/css/styles.css +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/audit.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/authentication.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/bulk_vs_transactional.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/cloud_works.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/index.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/logging.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/sorting.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/guides/transactional.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/img/anaplan-sdk.webp +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/index.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/installation.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/js/assets/hljs.js +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/js/assets/hljs.min.js +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/js/assets/python.js +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/js/assets/python.min.js +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/js/highlight.js +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/js/highlight.min.js +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/docs/quickstart.md +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/mkdocs.yml +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/async/test_async_alm_client.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/async/test_async_audit_client.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/async/test_async_cloud_works_client.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/async/test_async_flows_client.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/conftest.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/sync/test_alm_client.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/sync/test_audit_client.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/sync/test_cloud_works_client.py +0 -0
- {anaplan_sdk-0.5.0a4 → anaplan_sdk-0.5.0a6}/tests/sync/test_flows_client.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: anaplan-sdk
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.0a6
|
4
4
|
Summary: Streamlined Python Interface for 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
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
from typing import Literal, overload
|
3
3
|
|
4
|
-
from anaplan_sdk._services import _AsyncHttpService
|
4
|
+
from anaplan_sdk._services import _AsyncHttpService
|
5
|
+
from anaplan_sdk._utils import sort_params
|
5
6
|
from anaplan_sdk.exceptions import AnaplanActionError
|
6
7
|
from anaplan_sdk.models import (
|
7
8
|
ModelRevision,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from typing import Any, Literal
|
2
2
|
|
3
|
-
from anaplan_sdk._services import _AsyncHttpService
|
3
|
+
from anaplan_sdk._services import _AsyncHttpService
|
4
|
+
from anaplan_sdk._utils import sort_params
|
4
5
|
from anaplan_sdk.models import User
|
5
6
|
|
6
7
|
Event = Literal["all", "byok", "user_activity"]
|
@@ -21,7 +22,9 @@ class _AsyncAuditClient:
|
|
21
22
|
) -> list[User]:
|
22
23
|
"""
|
23
24
|
Lists all the Users in the authenticated users default tenant.
|
24
|
-
:param search_pattern:
|
25
|
+
:param search_pattern: **Caution: This is an undocumented Feature and may behave
|
26
|
+
unpredictably. It requires the Tenant Admin role. For non-admin users, it is
|
27
|
+
ignored.** Optionally filter for specific users. When provided,
|
25
28
|
case-insensitive matches users with emails or names containing this string.
|
26
29
|
You can use the wildcards `%` for 0-n characters, and `_` for exactly 1 character.
|
27
30
|
When None (default), returns all users.
|
@@ -7,7 +7,8 @@ import httpx
|
|
7
7
|
from typing_extensions import Self
|
8
8
|
|
9
9
|
from anaplan_sdk._auth import _create_auth
|
10
|
-
from anaplan_sdk._services import _AsyncHttpService
|
10
|
+
from anaplan_sdk._services import _AsyncHttpService
|
11
|
+
from anaplan_sdk._utils import action_url, models_url, sort_params
|
11
12
|
from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierException
|
12
13
|
from anaplan_sdk.models import (
|
13
14
|
Action,
|
@@ -142,34 +143,24 @@ class AsyncClient:
|
|
142
143
|
f"Initialized AsyncClient with workspace_id={workspace_id}, model_id={model_id}"
|
143
144
|
)
|
144
145
|
|
145
|
-
|
146
|
-
def from_existing(
|
147
|
-
cls, existing: Self, *, workspace_id: str | None = None, model_id: str | None = None
|
148
|
-
) -> Self:
|
146
|
+
def with_model(self, model_id: str | None = None, workspace_id: str | None = None) -> Self:
|
149
147
|
"""
|
150
|
-
Create a new instance of the Client
|
151
|
-
|
152
|
-
authentication and configuration. This creates a shallow copy of the existing client and
|
153
|
-
optionally updates the relevant attributes to the new workspace and model. You can provide
|
154
|
-
either a new workspace Id or a new model Id, or both. If you do not provide one of them,
|
155
|
-
the existing value will be used. If you omit both, the new instance will be an identical
|
156
|
-
copy of the existing instance.
|
157
|
-
|
158
|
-
:param existing: The existing instance to copy.
|
148
|
+
Create a new instance of the Client with the given model and workspace Ids. **This creates
|
149
|
+
a copy of the current client. The current instance remains unchanged.**
|
159
150
|
:param workspace_id: The workspace Id to use or None to use the existing workspace Id.
|
160
151
|
:param model_id: The model Id to use or None to use the existing model Id.
|
161
152
|
:return: A new instance of the Client.
|
162
153
|
"""
|
163
|
-
client = copy(
|
164
|
-
new_ws_id = workspace_id or
|
165
|
-
new_model_id = model_id or
|
154
|
+
client = copy(self)
|
155
|
+
new_ws_id = workspace_id or self._workspace_id
|
156
|
+
new_model_id = model_id or self._model_id
|
166
157
|
logger.debug(
|
167
158
|
f"Creating a new AsyncClient from existing instance "
|
168
159
|
f"with workspace_id={new_ws_id}, model_id={new_model_id}."
|
169
160
|
)
|
170
161
|
client._url = f"https://api.anaplan.com/2/0/workspaces/{new_ws_id}/models/{new_model_id}"
|
171
|
-
client._transactional_client = _AsyncTransactionalClient(
|
172
|
-
client._alm_client = _AsyncAlmClient(
|
162
|
+
client._transactional_client = _AsyncTransactionalClient(self._http, new_model_id)
|
163
|
+
client._alm_client = _AsyncAlmClient(self._http, new_model_id)
|
173
164
|
return client
|
174
165
|
|
175
166
|
@property
|
@@ -235,7 +226,9 @@ class AsyncClient:
|
|
235
226
|
) -> list[Workspace]:
|
236
227
|
"""
|
237
228
|
Lists all the Workspaces the authenticated user has access to.
|
238
|
-
:param search_pattern:
|
229
|
+
:param search_pattern: **Caution: This is an undocumented Feature and may behave
|
230
|
+
unpredictably. It requires the Tenant Admin role. For non-admin users, it is
|
231
|
+
ignored.** Optionally filter for specific workspaces. When provided,
|
239
232
|
case-insensitive matches workspaces with names containing this string.
|
240
233
|
You can use the wildcards `%` for 0-n characters, and `_` for exactly 1 character.
|
241
234
|
When None (default), returns all users.
|
@@ -253,13 +246,19 @@ class AsyncClient:
|
|
253
246
|
|
254
247
|
async def get_models(
|
255
248
|
self,
|
249
|
+
only_in_workspace: bool | str = False,
|
256
250
|
search_pattern: str | None = None,
|
257
251
|
sort_by: Literal["active_state", "name"] | None = None,
|
258
252
|
descending: bool = False,
|
259
253
|
) -> list[Model]:
|
260
254
|
"""
|
261
255
|
Lists all the Models the authenticated user has access to.
|
262
|
-
:param
|
256
|
+
:param only_in_workspace: If True, only lists models in the workspace provided when
|
257
|
+
instantiating the client. If a string is provided, only lists models in the workspace
|
258
|
+
with the given Id. If False (default), lists models in all workspaces the user
|
259
|
+
:param search_pattern: **Caution: This is an undocumented Feature and may behave
|
260
|
+
unpredictably. It requires the Tenant Admin role. For non-admin users, it is
|
261
|
+
ignored.** Optionally filter for specific models. When provided,
|
263
262
|
case-insensitive matches model names containing this string.
|
264
263
|
You can use the wildcards `%` for 0-n characters, and `_` for exactly 1 character.
|
265
264
|
When None (default), returns all models.
|
@@ -271,7 +270,7 @@ class AsyncClient:
|
|
271
270
|
if search_pattern:
|
272
271
|
params["s"] = search_pattern
|
273
272
|
res = await self._http.get_paginated(
|
274
|
-
|
273
|
+
models_url(only_in_workspace, self._workspace_id), "models", params=params
|
275
274
|
)
|
276
275
|
return [Model.model_validate(e) for e in res]
|
277
276
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
from typing import Any, Literal
|
3
3
|
|
4
|
-
from anaplan_sdk._services import
|
5
|
-
|
4
|
+
from anaplan_sdk._services import _AsyncHttpService
|
5
|
+
from anaplan_sdk._utils import (
|
6
6
|
connection_body_payload,
|
7
7
|
construct_payload,
|
8
8
|
integration_payload,
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
from typing import Any
|
3
3
|
|
4
|
-
from anaplan_sdk._services import _AsyncHttpService
|
4
|
+
from anaplan_sdk._services import _AsyncHttpService
|
5
|
+
from anaplan_sdk._utils import construct_payload
|
5
6
|
from anaplan_sdk.models.flows import Flow, FlowInput, FlowSummary
|
6
7
|
|
7
8
|
logger = logging.getLogger("anaplan_sdk")
|
@@ -3,8 +3,8 @@ from asyncio import gather
|
|
3
3
|
from itertools import chain
|
4
4
|
from typing import Any, Literal, overload
|
5
5
|
|
6
|
-
from anaplan_sdk._services import
|
7
|
-
|
6
|
+
from anaplan_sdk._services import _AsyncHttpService
|
7
|
+
from anaplan_sdk._utils import (
|
8
8
|
parse_calendar_response,
|
9
9
|
parse_insertion_response,
|
10
10
|
sort_params,
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
from typing import Literal, overload
|
3
3
|
|
4
|
-
from anaplan_sdk._services import _HttpService
|
4
|
+
from anaplan_sdk._services import _HttpService
|
5
|
+
from anaplan_sdk._utils import sort_params
|
5
6
|
from anaplan_sdk.exceptions import AnaplanActionError
|
6
7
|
from anaplan_sdk.models import (
|
7
8
|
ModelRevision,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from typing import Any, Literal
|
2
2
|
|
3
|
-
from anaplan_sdk._services import _HttpService
|
3
|
+
from anaplan_sdk._services import _HttpService
|
4
|
+
from anaplan_sdk._utils import sort_params
|
4
5
|
from anaplan_sdk.models import User
|
5
6
|
|
6
7
|
Event = Literal["all", "byok", "user_activity"]
|
@@ -21,7 +22,9 @@ class _AuditClient:
|
|
21
22
|
) -> list[User]:
|
22
23
|
"""
|
23
24
|
Lists all the Users in the authenticated users default tenant.
|
24
|
-
:param search_pattern:
|
25
|
+
:param search_pattern: **Caution: This is an undocumented Feature and may behave
|
26
|
+
unpredictably. It requires the Tenant Admin role. For non-admin users, it is
|
27
|
+
ignored.** Optionally filter for specific users. When provided,
|
25
28
|
case-insensitive matches users with emails or names containing this string.
|
26
29
|
You can use the wildcards `%` for 0-n characters, and `_` for exactly 1 character.
|
27
30
|
When None (default), returns all users.
|
@@ -8,7 +8,8 @@ import httpx
|
|
8
8
|
from typing_extensions import Self
|
9
9
|
|
10
10
|
from anaplan_sdk._auth import _create_auth
|
11
|
-
from anaplan_sdk._services import _HttpService
|
11
|
+
from anaplan_sdk._services import _HttpService
|
12
|
+
from anaplan_sdk._utils import action_url, models_url, sort_params
|
12
13
|
from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierException
|
13
14
|
from anaplan_sdk.models import (
|
14
15
|
Action,
|
@@ -147,34 +148,24 @@ class Client:
|
|
147
148
|
self.allow_file_creation = allow_file_creation
|
148
149
|
logger.debug(f"Initialized Client with workspace_id={workspace_id}, model_id={model_id}")
|
149
150
|
|
150
|
-
|
151
|
-
def from_existing(
|
152
|
-
cls, existing: Self, *, workspace_id: str | None = None, model_id: str | None = None
|
153
|
-
) -> Self:
|
151
|
+
def with_model(self, model_id: str | None = None, workspace_id: str | None = None) -> Self:
|
154
152
|
"""
|
155
|
-
Create a new instance of the Client
|
156
|
-
|
157
|
-
authentication and configuration. This creates a shallow copy of the existing client and
|
158
|
-
optionally updates the relevant attributes to the new workspace and model. You can provide
|
159
|
-
either a new workspace Id or a new model Id, or both. If you do not provide one of them,
|
160
|
-
the existing value will be used. If you omit both, the new instance will be an identical
|
161
|
-
copy of the existing instance.
|
162
|
-
|
163
|
-
:param existing: The existing instance to copy.
|
153
|
+
Create a new instance of the Client with the given model and workspace Ids. **This creates
|
154
|
+
a copy of the current client. The current instance remains unchanged.**
|
164
155
|
:param workspace_id: The workspace Id to use or None to use the existing workspace Id.
|
165
156
|
:param model_id: The model Id to use or None to use the existing model Id.
|
166
157
|
:return: A new instance of the Client.
|
167
158
|
"""
|
168
|
-
client = copy(
|
169
|
-
new_ws_id = workspace_id or
|
170
|
-
new_model_id = model_id or
|
159
|
+
client = copy(self)
|
160
|
+
new_ws_id = workspace_id or self._workspace_id
|
161
|
+
new_model_id = model_id or self._model_id
|
171
162
|
logger.debug(
|
172
163
|
f"Creating a new AsyncClient from existing instance "
|
173
164
|
f"with workspace_id={new_ws_id}, model_id={new_model_id}."
|
174
165
|
)
|
175
166
|
client._url = f"https://api.anaplan.com/2/0/workspaces/{new_ws_id}/models/{new_model_id}"
|
176
|
-
client._transactional_client = _TransactionalClient(
|
177
|
-
client._alm_client = _AlmClient(
|
167
|
+
client._transactional_client = _TransactionalClient(self._http, new_model_id)
|
168
|
+
client._alm_client = _AlmClient(self._http, new_model_id)
|
178
169
|
return client
|
179
170
|
|
180
171
|
@property
|
@@ -240,7 +231,9 @@ class Client:
|
|
240
231
|
) -> list[Workspace]:
|
241
232
|
"""
|
242
233
|
Lists all the Workspaces the authenticated user has access to.
|
243
|
-
:param search_pattern:
|
234
|
+
:param search_pattern: **Caution: This is an undocumented Feature and may behave
|
235
|
+
unpredictably. It requires the Tenant Admin role. For non-admin users, it is
|
236
|
+
ignored.** Optionally filter for specific workspaces. When provided,
|
244
237
|
case-insensitive matches workspaces with names containing this string.
|
245
238
|
You can use the wildcards `%` for 0-n characters, and `_` for exactly 1 character.
|
246
239
|
When None (default), returns all users.
|
@@ -258,13 +251,19 @@ class Client:
|
|
258
251
|
|
259
252
|
def get_models(
|
260
253
|
self,
|
254
|
+
only_in_workspace: bool | str = False,
|
261
255
|
search_pattern: str | None = None,
|
262
256
|
sort_by: Literal["active_state", "name"] | None = None,
|
263
257
|
descending: bool = False,
|
264
258
|
) -> list[Model]:
|
265
259
|
"""
|
266
260
|
Lists all the Models the authenticated user has access to.
|
267
|
-
:param
|
261
|
+
:param only_in_workspace: If True, only lists models in the workspace provided when
|
262
|
+
instantiating the client. If a string is provided, only lists models in the workspace
|
263
|
+
with the given Id. If False (default), lists models in all workspaces the user
|
264
|
+
:param search_pattern: **Caution: This is an undocumented Feature and may behave
|
265
|
+
unpredictably. It requires the Tenant Admin role. For non-admin users, it is
|
266
|
+
ignored.** Optionally filter for specific models. When provided,
|
268
267
|
case-insensitive matches model names containing this string.
|
269
268
|
You can use the wildcards `%` for 0-n characters, and `_` for exactly 1 character.
|
270
269
|
When None (default), returns all models.
|
@@ -276,7 +275,7 @@ class Client:
|
|
276
275
|
if search_pattern:
|
277
276
|
params["s"] = search_pattern
|
278
277
|
res = self._http.get_paginated(
|
279
|
-
|
278
|
+
models_url(only_in_workspace, self._workspace_id), "models", params=params
|
280
279
|
)
|
281
280
|
return [Model.model_validate(e) for e in res]
|
282
281
|
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
from typing import Any
|
3
3
|
|
4
|
-
from anaplan_sdk._services import _HttpService
|
4
|
+
from anaplan_sdk._services import _HttpService
|
5
|
+
from anaplan_sdk._utils import construct_payload
|
5
6
|
from anaplan_sdk.models.flows import Flow, FlowInput, FlowSummary
|
6
7
|
|
7
8
|
logger = logging.getLogger("anaplan_sdk")
|
@@ -3,8 +3,8 @@ from concurrent.futures import ThreadPoolExecutor
|
|
3
3
|
from itertools import chain
|
4
4
|
from typing import Any, Literal, overload
|
5
5
|
|
6
|
-
from anaplan_sdk._services import
|
7
|
-
|
6
|
+
from anaplan_sdk._services import _HttpService
|
7
|
+
from anaplan_sdk._utils import (
|
8
8
|
parse_calendar_response,
|
9
9
|
parse_insertion_response,
|
10
10
|
sort_params,
|
@@ -6,32 +6,13 @@ from concurrent.futures import ThreadPoolExecutor
|
|
6
6
|
from gzip import compress
|
7
7
|
from itertools import chain
|
8
8
|
from math import ceil
|
9
|
-
from typing import Any, Awaitable, Callable, Coroutine, Iterator,
|
9
|
+
from typing import Any, Awaitable, Callable, Coroutine, Iterator, TypeVar
|
10
10
|
|
11
11
|
import httpx
|
12
12
|
from httpx import HTTPError, Response
|
13
|
-
from pydantic.alias_generators import to_camel
|
14
13
|
|
15
14
|
from .exceptions import AnaplanException, AnaplanTimeoutException, InvalidIdentifierException
|
16
|
-
from .models import
|
17
|
-
AnaplanModel,
|
18
|
-
InsertionResult,
|
19
|
-
ModelCalendar,
|
20
|
-
MonthsQuartersYearsCalendar,
|
21
|
-
TaskSummary,
|
22
|
-
WeeksGeneralCalendar,
|
23
|
-
WeeksGroupingCalendar,
|
24
|
-
WeeksPeriodsCalendar,
|
25
|
-
)
|
26
|
-
from .models.cloud_works import (
|
27
|
-
AmazonS3ConnectionInput,
|
28
|
-
AzureBlobConnectionInput,
|
29
|
-
ConnectionBody,
|
30
|
-
GoogleBigQueryConnectionInput,
|
31
|
-
IntegrationInput,
|
32
|
-
IntegrationProcessInput,
|
33
|
-
ScheduleInput,
|
34
|
-
)
|
15
|
+
from .models import TaskSummary
|
35
16
|
|
36
17
|
SORT_WARNING = (
|
37
18
|
"If you are sorting by a field that is potentially ambiguous (e.g., name), the order of "
|
@@ -47,7 +28,6 @@ logger = logging.getLogger("anaplan_sdk")
|
|
47
28
|
_json_header = {"Content-Type": "application/json"}
|
48
29
|
_gzip_header = {"Content-Type": "application/x-gzip"}
|
49
30
|
|
50
|
-
T = TypeVar("T", bound=AnaplanModel)
|
51
31
|
Task = TypeVar("Task", bound=TaskSummary)
|
52
32
|
|
53
33
|
|
@@ -134,15 +114,7 @@ class _HttpService:
|
|
134
114
|
logger.debug(f"Fetching first page with limit={self._page_size} from {url}.")
|
135
115
|
kwargs["params"] = (kwargs.get("params") or {}) | {"limit": self._page_size}
|
136
116
|
res = self.get(url, **kwargs)
|
137
|
-
|
138
|
-
actual_page_size = res["meta"]["paging"]["currentPageSize"]
|
139
|
-
if actual_page_size < self._page_size and not actual_page_size == total_items:
|
140
|
-
logger.warning(
|
141
|
-
f"Page size {self._page_size} was silently truncated to {actual_page_size}."
|
142
|
-
f"Using the server-side enforced page size {actual_page_size} for further requests."
|
143
|
-
)
|
144
|
-
logger.debug(f"Found {total_items} total items, retrieved {len(first_page)} in first page.")
|
145
|
-
return first_page, total_items, actual_page_size
|
117
|
+
return _extract_first_page(res, result_key, self._page_size)
|
146
118
|
|
147
119
|
def __run_with_retry(self, func: Callable[..., Response], *args, **kwargs) -> Response:
|
148
120
|
for i in range(max(self._retry_count, 1)):
|
@@ -159,7 +131,7 @@ class _HttpService:
|
|
159
131
|
return response
|
160
132
|
except HTTPError as error:
|
161
133
|
if i >= self._retry_count - 1:
|
162
|
-
|
134
|
+
_raise_error(error)
|
163
135
|
url = args[0] or kwargs.get("url")
|
164
136
|
logger.info(f"Retrying for: {url}")
|
165
137
|
|
@@ -252,15 +224,7 @@ class _AsyncHttpService:
|
|
252
224
|
logger.debug(f"Fetching first page with limit={self._page_size} from {url}.")
|
253
225
|
kwargs["params"] = (kwargs.get("params") or {}) | {"limit": self._page_size}
|
254
226
|
res = await self.get(url, **kwargs)
|
255
|
-
|
256
|
-
actual_page_size = res["meta"]["paging"]["currentPageSize"]
|
257
|
-
if actual_page_size < self._page_size and not actual_page_size == total_items:
|
258
|
-
logger.warning(
|
259
|
-
f"Page size {self._page_size} was silently truncated to {actual_page_size}."
|
260
|
-
f"Using the server-side enforced page size {actual_page_size} for further requests."
|
261
|
-
)
|
262
|
-
logger.debug(f"Found {total_items} total items, retrieved {len(first_page)} in first page.")
|
263
|
-
return first_page, total_items, actual_page_size
|
227
|
+
return _extract_first_page(res, result_key, self._page_size)
|
264
228
|
|
265
229
|
async def _run_with_retry(
|
266
230
|
self, func: Callable[..., Coroutine[Any, Any, Response]], *args, **kwargs
|
@@ -279,109 +243,28 @@ class _AsyncHttpService:
|
|
279
243
|
return response
|
280
244
|
except HTTPError as error:
|
281
245
|
if i >= self._retry_count - 1:
|
282
|
-
|
246
|
+
_raise_error(error)
|
283
247
|
url = args[0] or kwargs.get("url")
|
284
248
|
logger.info(f"Retrying for: {url}")
|
285
249
|
|
286
250
|
raise AnaplanException("Exhausted all retries without a successful response or Error.")
|
287
251
|
|
288
252
|
|
289
|
-
def
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
return {"sort": f"{'-' if descending else '+'}{to_camel(sort_by)}"}
|
299
|
-
|
300
|
-
|
301
|
-
def construct_payload(model: Type[T], body: T | dict[str, Any]) -> dict[str, Any]:
|
302
|
-
"""
|
303
|
-
Construct a payload for the given model and body.
|
304
|
-
:param model: The model class to use for validation.
|
305
|
-
:param body: The body to validate and optionally convert to a dictionary.
|
306
|
-
:return: A dictionary representation of the validated body.
|
307
|
-
"""
|
308
|
-
if isinstance(body, dict):
|
309
|
-
body = model.model_validate(body)
|
310
|
-
return body.model_dump(exclude_none=True, by_alias=True)
|
311
|
-
|
312
|
-
|
313
|
-
def connection_body_payload(body: ConnectionBody | dict[str, Any]) -> dict[str, Any]:
|
314
|
-
"""
|
315
|
-
Construct a payload for the given integration body.
|
316
|
-
:param body: The body to validate and optionally convert to a dictionary.
|
317
|
-
:return: A dictionary representation of the validated body.
|
318
|
-
"""
|
319
|
-
if isinstance(body, dict):
|
320
|
-
if "sasToken" in body:
|
321
|
-
body = AzureBlobConnectionInput.model_validate(body)
|
322
|
-
elif "secretAccessKey" in body:
|
323
|
-
body = AmazonS3ConnectionInput.model_validate(body)
|
324
|
-
else:
|
325
|
-
body = GoogleBigQueryConnectionInput.model_validate(body)
|
326
|
-
return body.model_dump(exclude_none=True, by_alias=True)
|
327
|
-
|
328
|
-
|
329
|
-
def integration_payload(
|
330
|
-
body: IntegrationInput | IntegrationProcessInput | dict[str, Any],
|
331
|
-
) -> dict[str, Any]:
|
332
|
-
"""
|
333
|
-
Construct a payload for the given integration body.
|
334
|
-
:param body: The body to validate and optionally convert to a dictionary.
|
335
|
-
:return: A dictionary representation of the validated body.
|
336
|
-
"""
|
337
|
-
if isinstance(body, dict):
|
338
|
-
body = (
|
339
|
-
IntegrationInput.model_validate(body)
|
340
|
-
if "jobs" in body
|
341
|
-
else IntegrationProcessInput.model_validate(body)
|
253
|
+
def _extract_first_page(
|
254
|
+
res: dict[str, Any], result_key: str, page_size: int
|
255
|
+
) -> tuple[list[dict[str, Any]], int, int]:
|
256
|
+
total_items, first_page = res["meta"]["paging"]["totalSize"], res.get(result_key, [])
|
257
|
+
actual_page_size = res["meta"]["paging"]["currentPageSize"]
|
258
|
+
if actual_page_size < page_size and not actual_page_size == total_items:
|
259
|
+
logger.warning(
|
260
|
+
f"Page size {page_size} was silently truncated to {actual_page_size}."
|
261
|
+
f"Using the server-side enforced page size {actual_page_size} for further requests."
|
342
262
|
)
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
) -> dict[str, Any]:
|
349
|
-
"""
|
350
|
-
Construct a payload for the given integration ID and schedule.
|
351
|
-
:param integration_id: The ID of the integration.
|
352
|
-
:param schedule: The schedule to validate and optionally convert to a dictionary.
|
353
|
-
:return: A dictionary representation of the validated schedule.
|
354
|
-
"""
|
355
|
-
if isinstance(schedule, dict):
|
356
|
-
schedule = ScheduleInput.model_validate(schedule)
|
357
|
-
return {
|
358
|
-
"integrationId": integration_id,
|
359
|
-
"schedule": schedule.model_dump(exclude_none=True, by_alias=True),
|
360
|
-
}
|
361
|
-
|
362
|
-
|
363
|
-
def action_url(action_id: int) -> Literal["imports", "exports", "actions", "processes"]:
|
364
|
-
"""
|
365
|
-
Determine the type of action based on its identifier.
|
366
|
-
:param action_id: The identifier of the action.
|
367
|
-
:return: The type of action.
|
368
|
-
"""
|
369
|
-
if 12000000000 <= action_id < 113000000000:
|
370
|
-
return "imports"
|
371
|
-
if 116000000000 <= action_id < 117000000000:
|
372
|
-
return "exports"
|
373
|
-
if 117000000000 <= action_id < 118000000000:
|
374
|
-
return "actions"
|
375
|
-
if 118000000000 <= action_id < 119000000000:
|
376
|
-
return "processes"
|
377
|
-
raise InvalidIdentifierException(f"Action '{action_id}' is not a valid identifier.")
|
378
|
-
|
379
|
-
|
380
|
-
def raise_error(error: HTTPError) -> None:
|
381
|
-
"""
|
382
|
-
Raise an appropriate exception based on the error.
|
383
|
-
:param error: The error to raise an exception for.
|
384
|
-
"""
|
263
|
+
logger.debug(f"Found {total_items} total items, retrieved {len(first_page)} in first page.")
|
264
|
+
return first_page, total_items, actual_page_size
|
265
|
+
|
266
|
+
|
267
|
+
def _raise_error(error: HTTPError) -> None:
|
385
268
|
if isinstance(error, httpx.TimeoutException):
|
386
269
|
raise AnaplanTimeoutException from error
|
387
270
|
if isinstance(error, httpx.HTTPStatusError):
|
@@ -392,59 +275,3 @@ def raise_error(error: HTTPError) -> None:
|
|
392
275
|
|
393
276
|
logger.error(f"Error: {error}")
|
394
277
|
raise AnaplanException from error
|
395
|
-
|
396
|
-
|
397
|
-
def parse_calendar_response(data: dict) -> ModelCalendar:
|
398
|
-
"""
|
399
|
-
Parse calendar response and return appropriate calendar model.
|
400
|
-
:param data: The calendar data from the API response.
|
401
|
-
:return: The calendar settings of the model based on calendar type.
|
402
|
-
"""
|
403
|
-
calendar_data = data["modelCalendar"]
|
404
|
-
cal_type = calendar_data["calendarType"]
|
405
|
-
if cal_type == "Calendar Months/Quarters/Years":
|
406
|
-
return MonthsQuartersYearsCalendar.model_validate(calendar_data)
|
407
|
-
if cal_type == "Weeks: 4-4-5, 4-5-4 or 5-4-4":
|
408
|
-
return WeeksGroupingCalendar.model_validate(calendar_data)
|
409
|
-
if cal_type == "Weeks: General":
|
410
|
-
return WeeksGeneralCalendar.model_validate(calendar_data)
|
411
|
-
if cal_type == "Weeks: 13 4-week Periods":
|
412
|
-
return WeeksPeriodsCalendar.model_validate(calendar_data)
|
413
|
-
raise AnaplanException(
|
414
|
-
"Unknown calendar type encountered. Please report this issue: "
|
415
|
-
"https://github.com/VinzenzKlass/anaplan-sdk/issues/new"
|
416
|
-
)
|
417
|
-
|
418
|
-
|
419
|
-
def parse_insertion_response(data: list[dict]) -> InsertionResult:
|
420
|
-
failures, added, ignored, total = [], 0, 0, 0
|
421
|
-
for res in data:
|
422
|
-
failures.append(res.get("failures", []))
|
423
|
-
added += res.get("added", 0)
|
424
|
-
total += res.get("total", 0)
|
425
|
-
ignored += res.get("ignored", 0)
|
426
|
-
return InsertionResult(
|
427
|
-
added=added, ignored=ignored, total=total, failures=list(chain.from_iterable(failures))
|
428
|
-
)
|
429
|
-
|
430
|
-
|
431
|
-
def validate_dimension_id(dimension_id: int) -> int:
|
432
|
-
if not (
|
433
|
-
dimension_id == 101999999999
|
434
|
-
or 101_000_000_000 <= dimension_id < 102_000_000_000
|
435
|
-
or 109_000_000_000 <= dimension_id < 110_000_000_000
|
436
|
-
or 114_000_000_000 <= dimension_id < 115_000_000_000
|
437
|
-
):
|
438
|
-
raise InvalidIdentifierException(
|
439
|
-
"Invalid dimension_id. Must be a List (101xxxxxxxxx), List Subset (109xxxxxxxxx), "
|
440
|
-
"Line Item Subset (114xxxxxxxxx), or Users (101999999999)."
|
441
|
-
)
|
442
|
-
msg = (
|
443
|
-
"Using `get_dimension_items` for {} is discouraged. "
|
444
|
-
"Prefer `{}` for better performance and more details on the members."
|
445
|
-
)
|
446
|
-
if dimension_id == 101999999999:
|
447
|
-
logger.warning(msg.format("Users", "get_users"))
|
448
|
-
if 101000000000 <= dimension_id < 102000000000:
|
449
|
-
logger.warning(msg.format("Lists", "get_list_items"))
|
450
|
-
return dimension_id
|