anaplan-sdk 0.4.4a4__py3-none-any.whl → 0.5.0__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/_base.py DELETED
@@ -1,297 +0,0 @@
1
- import asyncio
2
- import logging
3
- import random
4
- import time
5
- from asyncio import gather
6
- from concurrent.futures import ThreadPoolExecutor
7
- from gzip import compress
8
- from itertools import chain
9
- from math import ceil
10
- from typing import Any, Callable, Coroutine, Iterator, Literal, Type, TypeVar
11
-
12
- import httpx
13
- from httpx import HTTPError, Response
14
-
15
- from .exceptions import AnaplanException, AnaplanTimeoutException, InvalidIdentifierException
16
- from .models import AnaplanModel
17
- from .models.cloud_works import (
18
- AmazonS3ConnectionInput,
19
- AzureBlobConnectionInput,
20
- ConnectionBody,
21
- GoogleBigQueryConnectionInput,
22
- IntegrationInput,
23
- IntegrationProcessInput,
24
- ScheduleInput,
25
- )
26
-
27
- logger = logging.getLogger("anaplan_sdk")
28
-
29
- _json_header = {"Content-Type": "application/json"}
30
- _gzip_header = {"Content-Type": "application/x-gzip"}
31
-
32
- T = TypeVar("T", bound=AnaplanModel)
33
-
34
-
35
- class _BaseClient:
36
- def __init__(self, retry_count: int, client: httpx.Client):
37
- self._retry_count = retry_count
38
- self._client = client
39
-
40
- def _get(self, url: str, **kwargs) -> dict[str, Any]:
41
- return self._run_with_retry(self._client.get, url, **kwargs).json()
42
-
43
- def _get_binary(self, url: str) -> bytes:
44
- return self._run_with_retry(self._client.get, url).content
45
-
46
- def _post(self, url: str, json: dict | list) -> dict[str, Any]:
47
- return self._run_with_retry(self._client.post, url, headers=_json_header, json=json).json()
48
-
49
- def _put(self, url: str, json: dict | list) -> dict[str, Any]:
50
- return (self._run_with_retry(self._client.put, url, headers=_json_header, json=json)).json()
51
-
52
- def _patch(self, url: str, json: dict | list) -> dict[str, Any]:
53
- return (
54
- self._run_with_retry(self._client.patch, url, headers=_json_header, json=json)
55
- ).json()
56
-
57
- def _delete(self, url: str) -> dict[str, Any]:
58
- return (self._run_with_retry(self._client.delete, url, headers=_json_header)).json()
59
-
60
- def _post_empty(self, url: str) -> dict[str, Any]:
61
- res = self._run_with_retry(self._client.post, url)
62
- return res.json() if res.num_bytes_downloaded > 0 else {}
63
-
64
- def _put_binary_gzip(self, url: str, content: bytes) -> Response:
65
- return self._run_with_retry(
66
- self._client.put, url, headers=_gzip_header, content=compress(content)
67
- )
68
-
69
- def __get_page(self, url: str, limit: int, offset: int, result_key: str, **kwargs) -> list:
70
- kwargs["params"] = kwargs.get("params") or {} | {"limit": limit, "offset": offset}
71
- return self._get(url, **kwargs).get(result_key, [])
72
-
73
- def __get_first_page(self, url: str, limit: int, result_key: str, **kwargs) -> tuple[list, int]:
74
- kwargs["params"] = kwargs.get("params") or {} | {"limit": limit}
75
- res = self._get(url, **kwargs)
76
- return res.get(result_key, []), res["meta"]["paging"]["totalSize"]
77
-
78
- def _get_paginated(
79
- self, url: str, result_key: str, page_size: int = 5_000, **kwargs
80
- ) -> Iterator[dict[str, Any]]:
81
- first_page, total_items = self.__get_first_page(url, page_size, result_key, **kwargs)
82
- if total_items <= page_size:
83
- return iter(first_page)
84
-
85
- with ThreadPoolExecutor() as executor:
86
- pages = executor.map(
87
- lambda n: self.__get_page(url, page_size, n * page_size, result_key, **kwargs),
88
- range(1, ceil(total_items / page_size)),
89
- )
90
-
91
- return chain(first_page, *pages)
92
-
93
- def _run_with_retry(self, func: Callable[..., Response], *args, **kwargs) -> Response:
94
- for i in range(max(self._retry_count, 1)):
95
- try:
96
- response = func(*args, **kwargs)
97
- if response.status_code == 429:
98
- if i >= self._retry_count - 1:
99
- raise AnaplanException("Rate limit exceeded.")
100
- backoff_time = max(i, 1) * random.randint(2, 5)
101
- logger.info(f"Rate limited. Retrying in {backoff_time} seconds.")
102
- time.sleep(backoff_time)
103
- continue
104
- response.raise_for_status()
105
- return response
106
- except HTTPError as error:
107
- if i >= self._retry_count - 1:
108
- raise_error(error)
109
- url = args[0] or kwargs.get("url")
110
- logger.info(f"Retrying for: {url}")
111
-
112
- raise AnaplanException("Exhausted all retries without a successful response or Error.")
113
-
114
-
115
- class _AsyncBaseClient:
116
- def __init__(self, retry_count: int, client: httpx.AsyncClient):
117
- self._retry_count = retry_count
118
- self._client = client
119
-
120
- async def _get(self, url: str, **kwargs) -> dict[str, Any]:
121
- return (await self._run_with_retry(self._client.get, url, **kwargs)).json()
122
-
123
- async def _get_binary(self, url: str) -> bytes:
124
- return (await self._run_with_retry(self._client.get, url)).content
125
-
126
- async def _post(self, url: str, json: dict | list) -> dict[str, Any]:
127
- return (
128
- await self._run_with_retry(self._client.post, url, headers=_json_header, json=json)
129
- ).json()
130
-
131
- async def _put(self, url: str, json: dict | list) -> dict[str, Any]:
132
- return (
133
- await self._run_with_retry(self._client.put, url, headers=_json_header, json=json)
134
- ).json()
135
-
136
- async def _patch(self, url: str, json: dict | list) -> dict[str, Any]:
137
- return (
138
- await self._run_with_retry(self._client.patch, url, headers=_json_header, json=json)
139
- ).json()
140
-
141
- async def _delete(self, url: str) -> dict[str, Any]:
142
- return (await self._run_with_retry(self._client.delete, url, headers=_json_header)).json()
143
-
144
- async def _post_empty(self, url: str) -> dict[str, Any]:
145
- res = await self._run_with_retry(self._client.post, url)
146
- return res.json() if res.num_bytes_downloaded > 0 else {}
147
-
148
- async def _put_binary_gzip(self, url: str, content: bytes) -> Response:
149
- return await self._run_with_retry(
150
- self._client.put, url, headers=_gzip_header, content=compress(content)
151
- )
152
-
153
- async def __get_page(
154
- self, url: str, limit: int, offset: int, result_key: str, **kwargs
155
- ) -> list:
156
- kwargs["params"] = kwargs.get("params") or {} | {"limit": limit, "offset": offset}
157
- return (await self._get(url, **kwargs)).get(result_key, [])
158
-
159
- async def __get_first_page(
160
- self, url: str, limit: int, result_key: str, **kwargs
161
- ) -> tuple[list, int]:
162
- kwargs["params"] = kwargs.get("params") or {} | {"limit": limit}
163
- res = await self._get(url, **kwargs)
164
- return res.get(result_key, []), res["meta"]["paging"]["totalSize"]
165
-
166
- async def _get_paginated(
167
- self, url: str, result_key: str, page_size: int = 5_000, **kwargs
168
- ) -> Iterator[dict[str, Any]]:
169
- first_page, total_items = await self.__get_first_page(url, page_size, result_key, **kwargs)
170
- if total_items <= page_size:
171
- return iter(first_page)
172
- pages = await gather(
173
- *(
174
- self.__get_page(url, page_size, n * page_size, result_key, **kwargs)
175
- for n in range(1, ceil(total_items / page_size))
176
- )
177
- )
178
- return chain(first_page, *pages)
179
-
180
- async def _run_with_retry(
181
- self, func: Callable[..., Coroutine[Any, Any, Response]], *args, **kwargs
182
- ) -> Response:
183
- for i in range(max(self._retry_count, 1)):
184
- try:
185
- response = await func(*args, **kwargs)
186
- if response.status_code == 429:
187
- if i >= self._retry_count - 1:
188
- raise AnaplanException("Rate limit exceeded.")
189
- backoff_time = (i + 1) * random.randint(3, 5)
190
- logger.info(f"Rate limited. Retrying in {backoff_time} seconds.")
191
- await asyncio.sleep(backoff_time)
192
- continue
193
- response.raise_for_status()
194
- return response
195
- except HTTPError as error:
196
- if i >= self._retry_count - 1:
197
- raise_error(error)
198
- url = args[0] or kwargs.get("url")
199
- logger.info(f"Retrying for: {url}")
200
-
201
- raise AnaplanException("Exhausted all retries without a successful response or Error.")
202
-
203
-
204
- def construct_payload(model: Type[T], body: T | dict[str, Any]) -> dict[str, Any]:
205
- """
206
- Construct a payload for the given model and body.
207
- :param model: The model class to use for validation.
208
- :param body: The body to validate and optionally convert to a dictionary.
209
- :return: A dictionary representation of the validated body.
210
- """
211
- if isinstance(body, dict):
212
- body = model.model_validate(body)
213
- return body.model_dump(exclude_none=True, by_alias=True)
214
-
215
-
216
- def connection_body_payload(body: ConnectionBody | dict[str, Any]) -> dict[str, Any]:
217
- """
218
- Construct a payload for the given integration body.
219
- :param body: The body to validate and optionally convert to a dictionary.
220
- :return: A dictionary representation of the validated body.
221
- """
222
- if isinstance(body, dict):
223
- if "sasToken" in body:
224
- body = AzureBlobConnectionInput.model_validate(body)
225
- elif "secretAccessKey" in body:
226
- body = AmazonS3ConnectionInput.model_validate(body)
227
- else:
228
- body = GoogleBigQueryConnectionInput.model_validate(body)
229
- return body.model_dump(exclude_none=True, by_alias=True)
230
-
231
-
232
- def integration_payload(
233
- body: IntegrationInput | IntegrationProcessInput | dict[str, Any],
234
- ) -> dict[str, Any]:
235
- """
236
- Construct a payload for the given integration body.
237
- :param body: The body to validate and optionally convert to a dictionary.
238
- :return: A dictionary representation of the validated body.
239
- """
240
- if isinstance(body, dict):
241
- body = (
242
- IntegrationInput.model_validate(body)
243
- if "jobs" in body
244
- else IntegrationProcessInput.model_validate(body)
245
- )
246
- return body.model_dump(exclude_none=True, by_alias=True)
247
-
248
-
249
- def schedule_payload(
250
- integration_id: str, schedule: ScheduleInput | dict[str, Any]
251
- ) -> dict[str, Any]:
252
- """
253
- Construct a payload for the given integration ID and schedule.
254
- :param integration_id: The ID of the integration.
255
- :param schedule: The schedule to validate and optionally convert to a dictionary.
256
- :return: A dictionary representation of the validated schedule.
257
- """
258
- if isinstance(schedule, dict):
259
- schedule = ScheduleInput.model_validate(schedule)
260
- return {
261
- "integrationId": integration_id,
262
- "schedule": schedule.model_dump(exclude_none=True, by_alias=True),
263
- }
264
-
265
-
266
- def action_url(action_id: int) -> Literal["imports", "exports", "actions", "processes"]:
267
- """
268
- Determine the type of action based on its identifier.
269
- :param action_id: The identifier of the action.
270
- :return: The type of action.
271
- """
272
- if 12000000000 <= action_id < 113000000000:
273
- return "imports"
274
- if 116000000000 <= action_id < 117000000000:
275
- return "exports"
276
- if 117000000000 <= action_id < 118000000000:
277
- return "actions"
278
- if 118000000000 <= action_id < 119000000000:
279
- return "processes"
280
- raise InvalidIdentifierException(f"Action '{action_id}' is not a valid identifier.")
281
-
282
-
283
- def raise_error(error: HTTPError) -> None:
284
- """
285
- Raise an appropriate exception based on the error.
286
- :param error: The error to raise an exception for.
287
- """
288
- if isinstance(error, httpx.TimeoutException):
289
- raise AnaplanTimeoutException from error
290
- if isinstance(error, httpx.HTTPStatusError):
291
- if error.response.status_code == 404:
292
- raise InvalidIdentifierException from error
293
- logger.error(f"Anaplan Error: [{error.response.status_code}]: {error.response.text}")
294
- raise AnaplanException(error.response.text) from error
295
-
296
- logger.error(f"Error: {error}")
297
- raise AnaplanException from error
@@ -1,30 +0,0 @@
1
- anaplan_sdk/__init__.py,sha256=WScEKtXlnRLjCb-j3qW9W4kEACTyPsTLFs-L54et2TQ,351
2
- anaplan_sdk/_auth.py,sha256=H5A_fujTMHf7J33w2jkqMYtlxKFwzqZgxFcBJHi8wvc,16612
3
- anaplan_sdk/_base.py,sha256=9CdLshORWsLixOyoFa3A0Bka5lhLwlZrQI5sEdBcGFI,12298
4
- anaplan_sdk/_oauth.py,sha256=AynlJDrGIinQT0jwxI2RSvtU4D7Wasyw3H1uicdlLVI,12672
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=O1_r-O1tNDq7vXRwE2UEFE5S2bPmPh4IAQPQ8bmZfQE,3297
8
- anaplan_sdk/_async_clients/_audit.py,sha256=a92RY0B3bWxp2CCAWjzqKfvBjG1LJGlai0Hn5qmwgF8,2312
9
- anaplan_sdk/_async_clients/_bulk.py,sha256=j0yMoM8NWQH9BsSQ4LRYt8djfd1d11vkjNfU8pUeGLU,23737
10
- anaplan_sdk/_async_clients/_cloud_works.py,sha256=KPX9W55SF6h8fJd4Rx-HLq6eaRA-Vo3rFu343UiiaGQ,16642
11
- anaplan_sdk/_async_clients/_cw_flow.py,sha256=ZTNAbKDwb59Wg3u68hbtt1kpd-LNz9K0sftT-gvYzJQ,3651
12
- anaplan_sdk/_async_clients/_transactional.py,sha256=Mvr7OyBPjQRpBtzkJNfRzV4aNCzUiaYmm0zQubo62Wo,8035
13
- anaplan_sdk/_clients/__init__.py,sha256=FsbwvZC1FHrxuRXwbPxUzbhz_lO1DpXIxEOjx6-3QuA,219
14
- anaplan_sdk/_clients/_alm.py,sha256=UAdQxgHfax-VquC0YtbqrRBku2Rn35tVgwJdxYFScps,3202
15
- anaplan_sdk/_clients/_audit.py,sha256=xQQiwWIb4QQefolPvxNwBFE-pkRzzi8fYPyewjF63lc,2181
16
- anaplan_sdk/_clients/_bulk.py,sha256=nlsZHK8vjhvyC0auRuqyvJVvTISPqj9EIHBYLoqSpOc,23354
17
- anaplan_sdk/_clients/_cloud_works.py,sha256=KAMnLoeMJ2iwMXlDSbKynCE57BtkCfOgM5O8wT1kkSs,16291
18
- anaplan_sdk/_clients/_cw_flow.py,sha256=5IFWFT-qbyGvaSOOtaFOjHnOlyYbj4Rj3xiavfTlm8c,3527
19
- anaplan_sdk/_clients/_transactional.py,sha256=YUVbA54uhMloQcahwMtmZO3YooO6qQzwZN3ZRSu_z_c,7976
20
- anaplan_sdk/models/__init__.py,sha256=nSplwPG_74CG9CKbv1PzP9bsA9v5-daS4azpTCvCQTI,925
21
- anaplan_sdk/models/_alm.py,sha256=IqsTPvkx_ujLpaqZgIrTcr44KHJyKc4dyeRs9rkDjms,2307
22
- anaplan_sdk/models/_base.py,sha256=6AZc9CfireUKgpZfMxYKu4MbwiyHQOsGLjKrxGXBLic,508
23
- anaplan_sdk/models/_bulk.py,sha256=_lHARGGjJgi-AmA7u5ZfCmGpLecPnr73LSAsZSX-a_A,8276
24
- anaplan_sdk/models/_transactional.py,sha256=_0UbVR9D5QABI29yloYrJTSgL-K0EU7PzPeJu5LdhnY,4854
25
- anaplan_sdk/models/cloud_works.py,sha256=nfn_LHPR-KmW7Tpvz-5qNCzmR8SYgvsVV-lx5iDlyqI,19425
26
- anaplan_sdk/models/flows.py,sha256=SuLgNj5-2SeE3U1i8iY8cq2IkjuUgd_3M1n2ENructk,3625
27
- anaplan_sdk-0.4.4a4.dist-info/METADATA,sha256=uf0KLkAvBJr3cY668F9T-wuIG5uya2zHvIRu-roipVo,3628
28
- anaplan_sdk-0.4.4a4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
- anaplan_sdk-0.4.4a4.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
30
- anaplan_sdk-0.4.4a4.dist-info/RECORD,,