anaplan-sdk 0.5.0a5__py3-none-any.whl → 0.5.0a6__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.
- anaplan_sdk/_async_clients/_alm.py +2 -1
- anaplan_sdk/_async_clients/_audit.py +2 -1
- anaplan_sdk/_async_clients/_bulk.py +2 -1
- anaplan_sdk/_async_clients/_cloud_works.py +2 -2
- anaplan_sdk/_async_clients/_cw_flow.py +2 -1
- anaplan_sdk/_async_clients/_transactional.py +2 -2
- anaplan_sdk/_clients/_alm.py +2 -1
- anaplan_sdk/_clients/_audit.py +2 -1
- anaplan_sdk/_clients/_bulk.py +2 -1
- anaplan_sdk/_clients/_cloud_works.py +2 -2
- anaplan_sdk/_clients/_cw_flow.py +2 -1
- anaplan_sdk/_clients/_transactional.py +2 -2
- anaplan_sdk/_services.py +20 -207
- anaplan_sdk/_utils.py +188 -0
- {anaplan_sdk-0.5.0a5.dist-info → anaplan_sdk-0.5.0a6.dist-info}/METADATA +1 -1
- anaplan_sdk-0.5.0a6.dist-info/RECORD +31 -0
- anaplan_sdk-0.5.0a5.dist-info/RECORD +0 -30
- {anaplan_sdk-0.5.0a5.dist-info → anaplan_sdk-0.5.0a6.dist-info}/WHEEL +0 -0
- {anaplan_sdk-0.5.0a5.dist-info → anaplan_sdk-0.5.0a6.dist-info}/licenses/LICENSE +0 -0
@@ -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"]
|
@@ -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,
|
@@ -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,
|
anaplan_sdk/_clients/_alm.py
CHANGED
@@ -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,
|
anaplan_sdk/_clients/_audit.py
CHANGED
@@ -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"]
|
anaplan_sdk/_clients/_bulk.py
CHANGED
@@ -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,
|
anaplan_sdk/_clients/_cw_flow.py
CHANGED
@@ -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,
|
anaplan_sdk/_services.py
CHANGED
@@ -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,123 +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
|
-
if isinstance(only_in_workspace, str):
|
299
|
-
return f"https://api.anaplan.com/2/0/workspaces/{only_in_workspace}/models"
|
300
|
-
return "https://api.anaplan.com/2/0/models"
|
301
|
-
|
302
|
-
|
303
|
-
def sort_params(sort_by: str | None, descending: bool) -> dict[str, str | bool]:
|
304
|
-
"""
|
305
|
-
Construct search parameters for sorting. This also converts snake_case to camelCase.
|
306
|
-
:param sort_by: The field to sort by, optionally in snake_case.
|
307
|
-
:param descending: Whether to sort in descending order.
|
308
|
-
:return: A dictionary of search parameters in Anaplan's expected format.
|
309
|
-
"""
|
310
|
-
if not sort_by:
|
311
|
-
return {}
|
312
|
-
return {"sort": f"{'-' if descending else '+'}{to_camel(sort_by)}"}
|
313
|
-
|
314
|
-
|
315
|
-
def construct_payload(model: Type[T], body: T | dict[str, Any]) -> dict[str, Any]:
|
316
|
-
"""
|
317
|
-
Construct a payload for the given model and body.
|
318
|
-
:param model: The model class to use for validation.
|
319
|
-
:param body: The body to validate and optionally convert to a dictionary.
|
320
|
-
:return: A dictionary representation of the validated body.
|
321
|
-
"""
|
322
|
-
if isinstance(body, dict):
|
323
|
-
body = model.model_validate(body)
|
324
|
-
return body.model_dump(exclude_none=True, by_alias=True)
|
325
|
-
|
326
|
-
|
327
|
-
def connection_body_payload(body: ConnectionBody | dict[str, Any]) -> dict[str, Any]:
|
328
|
-
"""
|
329
|
-
Construct a payload for the given integration body.
|
330
|
-
:param body: The body to validate and optionally convert to a dictionary.
|
331
|
-
:return: A dictionary representation of the validated body.
|
332
|
-
"""
|
333
|
-
if isinstance(body, dict):
|
334
|
-
if "sasToken" in body:
|
335
|
-
body = AzureBlobConnectionInput.model_validate(body)
|
336
|
-
elif "secretAccessKey" in body:
|
337
|
-
body = AmazonS3ConnectionInput.model_validate(body)
|
338
|
-
else:
|
339
|
-
body = GoogleBigQueryConnectionInput.model_validate(body)
|
340
|
-
return body.model_dump(exclude_none=True, by_alias=True)
|
341
|
-
|
342
|
-
|
343
|
-
def integration_payload(
|
344
|
-
body: IntegrationInput | IntegrationProcessInput | dict[str, Any],
|
345
|
-
) -> dict[str, Any]:
|
346
|
-
"""
|
347
|
-
Construct a payload for the given integration body.
|
348
|
-
:param body: The body to validate and optionally convert to a dictionary.
|
349
|
-
:return: A dictionary representation of the validated body.
|
350
|
-
"""
|
351
|
-
if isinstance(body, dict):
|
352
|
-
body = (
|
353
|
-
IntegrationInput.model_validate(body)
|
354
|
-
if "jobs" in body
|
355
|
-
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."
|
356
262
|
)
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
) -> dict[str, Any]:
|
363
|
-
"""
|
364
|
-
Construct a payload for the given integration ID and schedule.
|
365
|
-
:param integration_id: The ID of the integration.
|
366
|
-
:param schedule: The schedule to validate and optionally convert to a dictionary.
|
367
|
-
:return: A dictionary representation of the validated schedule.
|
368
|
-
"""
|
369
|
-
if isinstance(schedule, dict):
|
370
|
-
schedule = ScheduleInput.model_validate(schedule)
|
371
|
-
return {
|
372
|
-
"integrationId": integration_id,
|
373
|
-
"schedule": schedule.model_dump(exclude_none=True, by_alias=True),
|
374
|
-
}
|
375
|
-
|
376
|
-
|
377
|
-
def action_url(action_id: int) -> Literal["imports", "exports", "actions", "processes"]:
|
378
|
-
"""
|
379
|
-
Determine the type of action based on its identifier.
|
380
|
-
:param action_id: The identifier of the action.
|
381
|
-
:return: The type of action.
|
382
|
-
"""
|
383
|
-
if 12000000000 <= action_id < 113000000000:
|
384
|
-
return "imports"
|
385
|
-
if 116000000000 <= action_id < 117000000000:
|
386
|
-
return "exports"
|
387
|
-
if 117000000000 <= action_id < 118000000000:
|
388
|
-
return "actions"
|
389
|
-
if 118000000000 <= action_id < 119000000000:
|
390
|
-
return "processes"
|
391
|
-
raise InvalidIdentifierException(f"Action '{action_id}' is not a valid identifier.")
|
392
|
-
|
393
|
-
|
394
|
-
def raise_error(error: HTTPError) -> None:
|
395
|
-
"""
|
396
|
-
Raise an appropriate exception based on the error.
|
397
|
-
:param error: The error to raise an exception for.
|
398
|
-
"""
|
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:
|
399
268
|
if isinstance(error, httpx.TimeoutException):
|
400
269
|
raise AnaplanTimeoutException from error
|
401
270
|
if isinstance(error, httpx.HTTPStatusError):
|
@@ -406,59 +275,3 @@ def raise_error(error: HTTPError) -> None:
|
|
406
275
|
|
407
276
|
logger.error(f"Error: {error}")
|
408
277
|
raise AnaplanException from error
|
409
|
-
|
410
|
-
|
411
|
-
def parse_calendar_response(data: dict) -> ModelCalendar:
|
412
|
-
"""
|
413
|
-
Parse calendar response and return appropriate calendar model.
|
414
|
-
:param data: The calendar data from the API response.
|
415
|
-
:return: The calendar settings of the model based on calendar type.
|
416
|
-
"""
|
417
|
-
calendar_data = data["modelCalendar"]
|
418
|
-
cal_type = calendar_data["calendarType"]
|
419
|
-
if cal_type == "Calendar Months/Quarters/Years":
|
420
|
-
return MonthsQuartersYearsCalendar.model_validate(calendar_data)
|
421
|
-
if cal_type == "Weeks: 4-4-5, 4-5-4 or 5-4-4":
|
422
|
-
return WeeksGroupingCalendar.model_validate(calendar_data)
|
423
|
-
if cal_type == "Weeks: General":
|
424
|
-
return WeeksGeneralCalendar.model_validate(calendar_data)
|
425
|
-
if cal_type == "Weeks: 13 4-week Periods":
|
426
|
-
return WeeksPeriodsCalendar.model_validate(calendar_data)
|
427
|
-
raise AnaplanException(
|
428
|
-
"Unknown calendar type encountered. Please report this issue: "
|
429
|
-
"https://github.com/VinzenzKlass/anaplan-sdk/issues/new"
|
430
|
-
)
|
431
|
-
|
432
|
-
|
433
|
-
def parse_insertion_response(data: list[dict]) -> InsertionResult:
|
434
|
-
failures, added, ignored, total = [], 0, 0, 0
|
435
|
-
for res in data:
|
436
|
-
failures.append(res.get("failures", []))
|
437
|
-
added += res.get("added", 0)
|
438
|
-
total += res.get("total", 0)
|
439
|
-
ignored += res.get("ignored", 0)
|
440
|
-
return InsertionResult(
|
441
|
-
added=added, ignored=ignored, total=total, failures=list(chain.from_iterable(failures))
|
442
|
-
)
|
443
|
-
|
444
|
-
|
445
|
-
def validate_dimension_id(dimension_id: int) -> int:
|
446
|
-
if not (
|
447
|
-
dimension_id == 101999999999
|
448
|
-
or 101_000_000_000 <= dimension_id < 102_000_000_000
|
449
|
-
or 109_000_000_000 <= dimension_id < 110_000_000_000
|
450
|
-
or 114_000_000_000 <= dimension_id < 115_000_000_000
|
451
|
-
):
|
452
|
-
raise InvalidIdentifierException(
|
453
|
-
"Invalid dimension_id. Must be a List (101xxxxxxxxx), List Subset (109xxxxxxxxx), "
|
454
|
-
"Line Item Subset (114xxxxxxxxx), or Users (101999999999)."
|
455
|
-
)
|
456
|
-
msg = (
|
457
|
-
"Using `get_dimension_items` for {} is discouraged. "
|
458
|
-
"Prefer `{}` for better performance and more details on the members."
|
459
|
-
)
|
460
|
-
if dimension_id == 101999999999:
|
461
|
-
logger.warning(msg.format("Users", "get_users"))
|
462
|
-
if 101000000000 <= dimension_id < 102000000000:
|
463
|
-
logger.warning(msg.format("Lists", "get_list_items"))
|
464
|
-
return dimension_id
|
anaplan_sdk/_utils.py
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
from itertools import chain
|
2
|
+
from typing import Any, Literal, Type, TypeVar
|
3
|
+
|
4
|
+
from pydantic.alias_generators import to_camel
|
5
|
+
|
6
|
+
from anaplan_sdk._services import logger
|
7
|
+
from anaplan_sdk.exceptions import AnaplanException, InvalidIdentifierException
|
8
|
+
from anaplan_sdk.models import (
|
9
|
+
AnaplanModel,
|
10
|
+
InsertionResult,
|
11
|
+
ModelCalendar,
|
12
|
+
MonthsQuartersYearsCalendar,
|
13
|
+
WeeksGeneralCalendar,
|
14
|
+
WeeksGroupingCalendar,
|
15
|
+
WeeksPeriodsCalendar,
|
16
|
+
)
|
17
|
+
from anaplan_sdk.models.cloud_works import (
|
18
|
+
AmazonS3ConnectionInput,
|
19
|
+
AzureBlobConnectionInput,
|
20
|
+
ConnectionBody,
|
21
|
+
GoogleBigQueryConnectionInput,
|
22
|
+
IntegrationInput,
|
23
|
+
IntegrationProcessInput,
|
24
|
+
ScheduleInput,
|
25
|
+
)
|
26
|
+
|
27
|
+
T = TypeVar("T", bound=AnaplanModel)
|
28
|
+
|
29
|
+
|
30
|
+
def models_url(only_in_workspace: bool | str, workspace_id: str | None) -> str:
|
31
|
+
if isinstance(only_in_workspace, bool) and only_in_workspace:
|
32
|
+
if not workspace_id:
|
33
|
+
raise ValueError(
|
34
|
+
"Cannot list models in the current workspace, since no workspace Id was "
|
35
|
+
"provided when instantiating the client. Either provide a workspace Id when "
|
36
|
+
"instantiating the client, or pass a specific workspace Id to this method."
|
37
|
+
)
|
38
|
+
return f"https://api.anaplan.com/2/0/workspaces/{workspace_id}/models"
|
39
|
+
if isinstance(only_in_workspace, str):
|
40
|
+
return f"https://api.anaplan.com/2/0/workspaces/{only_in_workspace}/models"
|
41
|
+
return "https://api.anaplan.com/2/0/models"
|
42
|
+
|
43
|
+
|
44
|
+
def sort_params(sort_by: str | None, descending: bool) -> dict[str, str | bool]:
|
45
|
+
"""
|
46
|
+
Construct search parameters for sorting. This also converts snake_case to camelCase.
|
47
|
+
:param sort_by: The field to sort by, optionally in snake_case.
|
48
|
+
:param descending: Whether to sort in descending order.
|
49
|
+
:return: A dictionary of search parameters in Anaplan's expected format.
|
50
|
+
"""
|
51
|
+
if not sort_by:
|
52
|
+
return {}
|
53
|
+
return {"sort": f"{'-' if descending else '+'}{to_camel(sort_by)}"}
|
54
|
+
|
55
|
+
|
56
|
+
def construct_payload(model: Type[T], body: T | dict[str, Any]) -> dict[str, Any]:
|
57
|
+
"""
|
58
|
+
Construct a payload for the given model and body.
|
59
|
+
:param model: The model class to use for validation.
|
60
|
+
:param body: The body to validate and optionally convert to a dictionary.
|
61
|
+
:return: A dictionary representation of the validated body.
|
62
|
+
"""
|
63
|
+
if isinstance(body, dict):
|
64
|
+
body = model.model_validate(body)
|
65
|
+
return body.model_dump(exclude_none=True, by_alias=True)
|
66
|
+
|
67
|
+
|
68
|
+
def connection_body_payload(body: ConnectionBody | dict[str, Any]) -> dict[str, Any]:
|
69
|
+
"""
|
70
|
+
Construct a payload for the given integration body.
|
71
|
+
:param body: The body to validate and optionally convert to a dictionary.
|
72
|
+
:return: A dictionary representation of the validated body.
|
73
|
+
"""
|
74
|
+
if isinstance(body, dict):
|
75
|
+
if "sasToken" in body:
|
76
|
+
body = AzureBlobConnectionInput.model_validate(body)
|
77
|
+
elif "secretAccessKey" in body:
|
78
|
+
body = AmazonS3ConnectionInput.model_validate(body)
|
79
|
+
else:
|
80
|
+
body = GoogleBigQueryConnectionInput.model_validate(body)
|
81
|
+
return body.model_dump(exclude_none=True, by_alias=True)
|
82
|
+
|
83
|
+
|
84
|
+
def integration_payload(
|
85
|
+
body: IntegrationInput | IntegrationProcessInput | dict[str, Any],
|
86
|
+
) -> dict[str, Any]:
|
87
|
+
"""
|
88
|
+
Construct a payload for the given integration body.
|
89
|
+
:param body: The body to validate and optionally convert to a dictionary.
|
90
|
+
:return: A dictionary representation of the validated body.
|
91
|
+
"""
|
92
|
+
if isinstance(body, dict):
|
93
|
+
body = (
|
94
|
+
IntegrationInput.model_validate(body)
|
95
|
+
if "jobs" in body
|
96
|
+
else IntegrationProcessInput.model_validate(body)
|
97
|
+
)
|
98
|
+
return body.model_dump(exclude_none=True, by_alias=True)
|
99
|
+
|
100
|
+
|
101
|
+
def schedule_payload(
|
102
|
+
integration_id: str, schedule: ScheduleInput | dict[str, Any]
|
103
|
+
) -> dict[str, Any]:
|
104
|
+
"""
|
105
|
+
Construct a payload for the given integration ID and schedule.
|
106
|
+
:param integration_id: The ID of the integration.
|
107
|
+
:param schedule: The schedule to validate and optionally convert to a dictionary.
|
108
|
+
:return: A dictionary representation of the validated schedule.
|
109
|
+
"""
|
110
|
+
if isinstance(schedule, dict):
|
111
|
+
schedule = ScheduleInput.model_validate(schedule)
|
112
|
+
return {
|
113
|
+
"integrationId": integration_id,
|
114
|
+
"schedule": schedule.model_dump(exclude_none=True, by_alias=True),
|
115
|
+
}
|
116
|
+
|
117
|
+
|
118
|
+
def action_url(action_id: int) -> Literal["imports", "exports", "actions", "processes"]:
|
119
|
+
"""
|
120
|
+
Determine the type of action based on its identifier.
|
121
|
+
:param action_id: The identifier of the action.
|
122
|
+
:return: The type of action.
|
123
|
+
"""
|
124
|
+
if 12000000000 <= action_id < 113000000000:
|
125
|
+
return "imports"
|
126
|
+
if 116000000000 <= action_id < 117000000000:
|
127
|
+
return "exports"
|
128
|
+
if 117000000000 <= action_id < 118000000000:
|
129
|
+
return "actions"
|
130
|
+
if 118000000000 <= action_id < 119000000000:
|
131
|
+
return "processes"
|
132
|
+
raise InvalidIdentifierException(f"Action '{action_id}' is not a valid identifier.")
|
133
|
+
|
134
|
+
|
135
|
+
def parse_calendar_response(data: dict) -> ModelCalendar:
|
136
|
+
"""
|
137
|
+
Parse calendar response and return appropriate calendar model.
|
138
|
+
:param data: The calendar data from the API response.
|
139
|
+
:return: The calendar settings of the model based on calendar type.
|
140
|
+
"""
|
141
|
+
calendar_data = data["modelCalendar"]
|
142
|
+
cal_type = calendar_data["calendarType"]
|
143
|
+
if cal_type == "Calendar Months/Quarters/Years":
|
144
|
+
return MonthsQuartersYearsCalendar.model_validate(calendar_data)
|
145
|
+
if cal_type == "Weeks: 4-4-5, 4-5-4 or 5-4-4":
|
146
|
+
return WeeksGroupingCalendar.model_validate(calendar_data)
|
147
|
+
if cal_type == "Weeks: General":
|
148
|
+
return WeeksGeneralCalendar.model_validate(calendar_data)
|
149
|
+
if cal_type == "Weeks: 13 4-week Periods":
|
150
|
+
return WeeksPeriodsCalendar.model_validate(calendar_data)
|
151
|
+
raise AnaplanException(
|
152
|
+
"Unknown calendar type encountered. Please report this issue: "
|
153
|
+
"https://github.com/VinzenzKlass/anaplan-sdk/issues/new"
|
154
|
+
)
|
155
|
+
|
156
|
+
|
157
|
+
def parse_insertion_response(data: list[dict]) -> InsertionResult:
|
158
|
+
failures, added, ignored, total = [], 0, 0, 0
|
159
|
+
for res in data:
|
160
|
+
failures.append(res.get("failures", []))
|
161
|
+
added += res.get("added", 0)
|
162
|
+
total += res.get("total", 0)
|
163
|
+
ignored += res.get("ignored", 0)
|
164
|
+
return InsertionResult(
|
165
|
+
added=added, ignored=ignored, total=total, failures=list(chain.from_iterable(failures))
|
166
|
+
)
|
167
|
+
|
168
|
+
|
169
|
+
def validate_dimension_id(dimension_id: int) -> int:
|
170
|
+
if not (
|
171
|
+
dimension_id == 101999999999
|
172
|
+
or 101_000_000_000 <= dimension_id < 102_000_000_000
|
173
|
+
or 109_000_000_000 <= dimension_id < 110_000_000_000
|
174
|
+
or 114_000_000_000 <= dimension_id < 115_000_000_000
|
175
|
+
):
|
176
|
+
raise InvalidIdentifierException(
|
177
|
+
"Invalid dimension_id. Must be a List (101xxxxxxxxx), List Subset (109xxxxxxxxx), "
|
178
|
+
"Line Item Subset (114xxxxxxxxx), or Users (101999999999)."
|
179
|
+
)
|
180
|
+
msg = (
|
181
|
+
"Using `get_dimension_items` for {} is discouraged. "
|
182
|
+
"Prefer `{}` for better performance and more details on the members."
|
183
|
+
)
|
184
|
+
if dimension_id == 101999999999:
|
185
|
+
logger.warning(msg.format("Users", "get_users"))
|
186
|
+
if 101000000000 <= dimension_id < 102000000000:
|
187
|
+
logger.warning(msg.format("Lists", "get_list_items"))
|
188
|
+
return dimension_id
|
@@ -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
|
@@ -0,0 +1,31 @@
|
|
1
|
+
anaplan_sdk/__init__.py,sha256=WScEKtXlnRLjCb-j3qW9W4kEACTyPsTLFs-L54et2TQ,351
|
2
|
+
anaplan_sdk/_auth.py,sha256=l5z2WCcfQ05OkuQ1dcmikp6dB87Rw1qy2zu8bbaAQTs,16620
|
3
|
+
anaplan_sdk/_oauth.py,sha256=AynlJDrGIinQT0jwxI2RSvtU4D7Wasyw3H1uicdlLVI,12672
|
4
|
+
anaplan_sdk/_services.py,sha256=n14DvTC2I5ub7yzjWBaAAX9YMF7LwI_BU7zuofAj0l4,13026
|
5
|
+
anaplan_sdk/_utils.py,sha256=jsrhdfLpriMoANukVvXpjpEJ5hWDNx7ZJKAguLvKgJA,7517
|
6
|
+
anaplan_sdk/exceptions.py,sha256=ALkA9fBF0NQ7dufFxV6AivjmHyuJk9DOQ9jtJV2n7f0,1809
|
7
|
+
anaplan_sdk/_async_clients/__init__.py,sha256=pZXgMMg4S9Aj_pxQCaSiPuNG-sePVGBtNJ0133VjqW4,364
|
8
|
+
anaplan_sdk/_async_clients/_alm.py,sha256=_PTD9Eght879HAadjcsfdvS0KCN93jgwpPOF8r3_E14,13178
|
9
|
+
anaplan_sdk/_async_clients/_audit.py,sha256=MkCTt7s6039GfDoyyINmDA1vAkZn2xPL4hFK8D4CYsE,2927
|
10
|
+
anaplan_sdk/_async_clients/_bulk.py,sha256=TsUUIGK_RPo1yKUyEbaGoFXFMBd0H4YHXI-ABYieZog,30100
|
11
|
+
anaplan_sdk/_async_clients/_cloud_works.py,sha256=aSgmJQvE7dSJawwK0A7GEBWs7wokWk7eCwRiQuiVg6I,17701
|
12
|
+
anaplan_sdk/_async_clients/_cw_flow.py,sha256=qJJFfnwLR7zIdZ_ay4fVI9zr3eP5B-qMcs4GlC9vqQY,3966
|
13
|
+
anaplan_sdk/_async_clients/_transactional.py,sha256=f-NlIjvRJ0NIKRcI6ZaO2YatLERwIaC0TY0fKvgUJ5Q,18050
|
14
|
+
anaplan_sdk/_clients/__init__.py,sha256=FsbwvZC1FHrxuRXwbPxUzbhz_lO1DpXIxEOjx6-3QuA,219
|
15
|
+
anaplan_sdk/_clients/_alm.py,sha256=oRHTjnCuwQYZDE2CBWA1u410jSImIroCsDOSeP9U9w8,12905
|
16
|
+
anaplan_sdk/_clients/_audit.py,sha256=Am6VviRw88_VC5i0N7TQTXEm6pJAHZb7xNaJTljD0fc,2850
|
17
|
+
anaplan_sdk/_clients/_bulk.py,sha256=9kN8LU3c0iDF-Ov47utBl-dj_hxX3twf7TOcolzogyI,30257
|
18
|
+
anaplan_sdk/_clients/_cloud_works.py,sha256=1bbMYM_g8MSorxTI9Au_dFzbJZgKGJVBE6DYlbWBR0U,17492
|
19
|
+
anaplan_sdk/_clients/_cw_flow.py,sha256=x64Ua2FwCpt8vab6gaLV8tDwW_ugJrDfU5dv-TnmM2M,3855
|
20
|
+
anaplan_sdk/_clients/_transactional.py,sha256=IgkvBaq1Ep5mB-uxu6QoE17cUCfsodvV8dppASQrIT4,17875
|
21
|
+
anaplan_sdk/models/__init__.py,sha256=zfwDQJQrXuLEXSpbJcm08a_YK1P7a7u-kMhwtJiJFmA,1783
|
22
|
+
anaplan_sdk/models/_alm.py,sha256=oeENd0YM7-LoIRBq2uATIQTxVgIP9rXx3UZE2UnQAp0,4670
|
23
|
+
anaplan_sdk/models/_base.py,sha256=6AZc9CfireUKgpZfMxYKu4MbwiyHQOsGLjKrxGXBLic,508
|
24
|
+
anaplan_sdk/models/_bulk.py,sha256=C0s6XdvHxuJHrPXU-pnZ1JXK1PJOl9FScHArpaox_mQ,8489
|
25
|
+
anaplan_sdk/models/_transactional.py,sha256=2bH10zvtMb5Lfh6DC7iQk72aEwq6tyLQ-XnH_0wYSqI,14172
|
26
|
+
anaplan_sdk/models/cloud_works.py,sha256=APUGDt_e-JshtXkba5cQh5rZkXOZBz0Aix0qVNdEWgw,19501
|
27
|
+
anaplan_sdk/models/flows.py,sha256=SuLgNj5-2SeE3U1i8iY8cq2IkjuUgd_3M1n2ENructk,3625
|
28
|
+
anaplan_sdk-0.5.0a6.dist-info/METADATA,sha256=VZfvzZQlLtBEOFFXMexsiEaetmGPe8fLilZBewT7iOg,3678
|
29
|
+
anaplan_sdk-0.5.0a6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
30
|
+
anaplan_sdk-0.5.0a6.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
31
|
+
anaplan_sdk-0.5.0a6.dist-info/RECORD,,
|
@@ -1,30 +0,0 @@
|
|
1
|
-
anaplan_sdk/__init__.py,sha256=WScEKtXlnRLjCb-j3qW9W4kEACTyPsTLFs-L54et2TQ,351
|
2
|
-
anaplan_sdk/_auth.py,sha256=l5z2WCcfQ05OkuQ1dcmikp6dB87Rw1qy2zu8bbaAQTs,16620
|
3
|
-
anaplan_sdk/_oauth.py,sha256=AynlJDrGIinQT0jwxI2RSvtU4D7Wasyw3H1uicdlLVI,12672
|
4
|
-
anaplan_sdk/_services.py,sha256=D54hGrzj1MSj7_6-WDZUZsoLcThI_neW4aIoDOqALjE,20869
|
5
|
-
anaplan_sdk/exceptions.py,sha256=ALkA9fBF0NQ7dufFxV6AivjmHyuJk9DOQ9jtJV2n7f0,1809
|
6
|
-
anaplan_sdk/_async_clients/__init__.py,sha256=pZXgMMg4S9Aj_pxQCaSiPuNG-sePVGBtNJ0133VjqW4,364
|
7
|
-
anaplan_sdk/_async_clients/_alm.py,sha256=zvKEvXlxNkcQim_XvyZLCbDafFldljg8APHqhAAIfvw,13147
|
8
|
-
anaplan_sdk/_async_clients/_audit.py,sha256=r3pJ4iHmXHyfDu19L80UTZzMReg6FKE7DlzyhXLhI8A,2896
|
9
|
-
anaplan_sdk/_async_clients/_bulk.py,sha256=JkXZ6mtK04bATyVZA6yA8akjVerurRjgINCViXAwoWM,30069
|
10
|
-
anaplan_sdk/_async_clients/_cloud_works.py,sha256=VB4l93426A0Xes5dZ6DsDu0go-BVNhs2RZn2zX5DSOc,17675
|
11
|
-
anaplan_sdk/_async_clients/_cw_flow.py,sha256=_allKIOP-qb33wrOj6GV5VAOvrCXOVJ1QXvck-jsocQ,3935
|
12
|
-
anaplan_sdk/_async_clients/_transactional.py,sha256=U6X5pW7By387JOgvHx-GmgVRi7MRJKALpx0lWI6xRMo,18024
|
13
|
-
anaplan_sdk/_clients/__init__.py,sha256=FsbwvZC1FHrxuRXwbPxUzbhz_lO1DpXIxEOjx6-3QuA,219
|
14
|
-
anaplan_sdk/_clients/_alm.py,sha256=3U7Cy5U5TsePF1YPogXvsOzNeQlQm_ezO5TlmD-Xbbs,12874
|
15
|
-
anaplan_sdk/_clients/_audit.py,sha256=oF1-7rGfYWG6LfM-i0vJzgpx4NAsLczo4welJR14N-U,2819
|
16
|
-
anaplan_sdk/_clients/_bulk.py,sha256=-I0e5yBHhQPIuS9pF_dRRi7OmY7H88P5mGPusGupKi4,30226
|
17
|
-
anaplan_sdk/_clients/_cloud_works.py,sha256=FsCp2wPxIoArAN1vcIfOI6ANNkK2ZebQ4MWJZB-nFJU,17466
|
18
|
-
anaplan_sdk/_clients/_cw_flow.py,sha256=O6t4utbDZdSVXGC0PXUcPpQ4oXrPohU9_8SUBCpxTXw,3824
|
19
|
-
anaplan_sdk/_clients/_transactional.py,sha256=SaHAnaGLZrhXmM8d6JnWWkwf-sVCEDW0nL2a4_wvjfk,17849
|
20
|
-
anaplan_sdk/models/__init__.py,sha256=zfwDQJQrXuLEXSpbJcm08a_YK1P7a7u-kMhwtJiJFmA,1783
|
21
|
-
anaplan_sdk/models/_alm.py,sha256=oeENd0YM7-LoIRBq2uATIQTxVgIP9rXx3UZE2UnQAp0,4670
|
22
|
-
anaplan_sdk/models/_base.py,sha256=6AZc9CfireUKgpZfMxYKu4MbwiyHQOsGLjKrxGXBLic,508
|
23
|
-
anaplan_sdk/models/_bulk.py,sha256=C0s6XdvHxuJHrPXU-pnZ1JXK1PJOl9FScHArpaox_mQ,8489
|
24
|
-
anaplan_sdk/models/_transactional.py,sha256=2bH10zvtMb5Lfh6DC7iQk72aEwq6tyLQ-XnH_0wYSqI,14172
|
25
|
-
anaplan_sdk/models/cloud_works.py,sha256=APUGDt_e-JshtXkba5cQh5rZkXOZBz0Aix0qVNdEWgw,19501
|
26
|
-
anaplan_sdk/models/flows.py,sha256=SuLgNj5-2SeE3U1i8iY8cq2IkjuUgd_3M1n2ENructk,3625
|
27
|
-
anaplan_sdk-0.5.0a5.dist-info/METADATA,sha256=cPMNYurLnr08YAdhsgOUFlrcw-xehz2gd5MQF_TrFg8,3678
|
28
|
-
anaplan_sdk-0.5.0a5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
29
|
-
anaplan_sdk-0.5.0a5.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
30
|
-
anaplan_sdk-0.5.0a5.dist-info/RECORD,,
|
File without changes
|
File without changes
|