anaplan-sdk 0.4.1__py3-none-any.whl → 0.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  import logging
2
2
  from asyncio import gather, sleep
3
3
  from copy import copy
4
- from typing import AsyncIterator, Callable, Iterator
4
+ from typing import AsyncIterator, Iterator
5
5
 
6
6
  import httpx
7
7
  from typing_extensions import Self
8
8
 
9
- from anaplan_sdk._auth import create_auth
9
+ from anaplan_sdk._auth import AuthCodeCallback, AuthTokenRefreshCallback, create_auth
10
10
  from anaplan_sdk._base import _AsyncBaseClient, action_url
11
11
  from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierException
12
12
  from anaplan_sdk.models import (
@@ -56,8 +56,8 @@ class AsyncClient(_AsyncBaseClient):
56
56
  redirect_uri: str | None = None,
57
57
  refresh_token: str | None = None,
58
58
  oauth2_scope: str = "openid profile email offline_access",
59
- on_auth_code: Callable[[str], str] | None = None,
60
- on_token_refresh: Callable[[dict[str, str]], None] | None = None,
59
+ on_auth_code: AuthCodeCallback = None,
60
+ on_token_refresh: AuthTokenRefreshCallback = None,
61
61
  timeout: float | httpx.Timeout = 30,
62
62
  retry_count: int = 2,
63
63
  status_poll_delay: int = 1,
@@ -94,12 +94,25 @@ class AsyncClient(_AsyncBaseClient):
94
94
  :param oauth2_scope: The scope of the Oauth2 token, if you want to narrow it.
95
95
  :param on_auth_code: A callback that takes the redirect URI as a single argument and must
96
96
  return the entire response URI. This will substitute the interactive
97
- authentication code step in the terminal.
97
+ authentication code step in the terminal. The callback can be either
98
+ a synchronous function or an async coroutine function - both will be
99
+ handled appropriately regardless of the execution context (in a thread,
100
+ with or without an event loop, etc.).
101
+ **Note**: When using asynchronous callbacks in complex applications
102
+ with multiple event loops, be aware that callbacks may execute in a
103
+ separate event loop context from where they were defined, which can
104
+ make debugging challenging.
98
105
  :param on_token_refresh: A callback function that is called whenever the token is refreshed.
106
+ This includes the initial token retrieval and any subsequent calls.
99
107
  With this you can for example securely store the token in your
100
108
  application or on your server for later reuse. The function
101
109
  must accept a single argument, which is the token dictionary
102
110
  returned by the Oauth2 token endpoint and does not return anything.
111
+ This can be either a synchronous function or an async coroutine
112
+ function. **Note**: When using asynchronous callbacks in complex
113
+ applications with multiple event loops, be aware that callbacks
114
+ may execute in a separate event loop context from where they were
115
+ defined, which can make debugging challenging.
103
116
  :param timeout: The timeout in seconds for the HTTP requests. Alternatively, you can pass
104
117
  an instance of `httpx.Timeout` to set the timeout for the HTTP requests.
105
118
  :param retry_count: The number of times to retry an HTTP request if it fails. Set this to 0
anaplan_sdk/_auth.py CHANGED
@@ -1,7 +1,11 @@
1
+ import asyncio
2
+ import inspect
1
3
  import logging
2
4
  import os
5
+ import threading
3
6
  from base64 import b64encode
4
- from typing import Callable
7
+ from concurrent.futures import ThreadPoolExecutor
8
+ from typing import Any, Awaitable, Callable, Coroutine
5
9
 
6
10
  import httpx
7
11
 
@@ -9,6 +13,11 @@ from .exceptions import AnaplanException, InvalidCredentialsException, InvalidPr
9
13
 
10
14
  logger = logging.getLogger("anaplan_sdk")
11
15
 
16
+ AuthCodeCallback = (Callable[[str], str] | Callable[[str], Awaitable[str]]) | None
17
+ AuthTokenRefreshCallback = (
18
+ Callable[[dict[str, str]], None] | Callable[[dict[str, str]], Awaitable[None]]
19
+ ) | None
20
+
12
21
 
13
22
  class _AnaplanAuth(httpx.Auth):
14
23
  requires_response_body = True
@@ -144,8 +153,8 @@ class AnaplanOauth2AuthCodeAuth(_AnaplanAuth):
144
153
  redirect_uri: str,
145
154
  refresh_token: str | None = None,
146
155
  scope: str = "openid profile email offline_access",
147
- on_auth_code: Callable[[str], str] | None = None,
148
- on_token_refresh: Callable[[dict[str, str]], None] | None = None,
156
+ on_auth_code: AuthCodeCallback = None,
157
+ on_token_refresh: AuthTokenRefreshCallback = None,
149
158
  ):
150
159
  try:
151
160
  from oauthlib.oauth2 import WebApplicationClient
@@ -188,7 +197,7 @@ class AnaplanOauth2AuthCodeAuth(_AnaplanAuth):
188
197
  self._token = token["access_token"]
189
198
  self._refresh_token = token["refresh_token"]
190
199
  if self._on_token_refresh:
191
- self._on_token_refresh(token)
200
+ _run_callback(self._on_token_refresh, token)
192
201
  self._id_token = token.get("id_token")
193
202
 
194
203
  def __auth_code_flow(self):
@@ -202,7 +211,7 @@ class AnaplanOauth2AuthCodeAuth(_AnaplanAuth):
202
211
  scope=self._scope,
203
212
  )
204
213
  authorization_response = (
205
- self._on_auth_code(url)
214
+ _run_callback(self._on_auth_code, url)
206
215
  if self._on_auth_code
207
216
  else input(
208
217
  f"Please go to {url} and authorize the app.\n"
@@ -231,8 +240,8 @@ def create_auth(
231
240
  redirect_uri: str | None = None,
232
241
  refresh_token: str | None = None,
233
242
  oauth2_scope: str = "openid profile email offline_access",
234
- on_auth_code: Callable[[str], str] | None = None,
235
- on_token_refresh: Callable[[dict[str, str]], None] | None = None,
243
+ on_auth_code: AuthCodeCallback = None,
244
+ on_token_refresh: AuthTokenRefreshCallback = None,
236
245
  ) -> _AnaplanAuth:
237
246
  if certificate and private_key:
238
247
  return AnaplanCertAuth(certificate, private_key, private_key_password)
@@ -254,3 +263,32 @@ def create_auth(
254
263
  "- certificate and private_key, or\n"
255
264
  "- client_id, client_secret, and redirect_uri"
256
265
  )
266
+
267
+
268
+ def _run_callback(func, *arg, **kwargs):
269
+ if not inspect.iscoroutinefunction(func):
270
+ return func(*arg, **kwargs)
271
+ coro = func(*arg, **kwargs)
272
+ try:
273
+ loop = asyncio.get_running_loop()
274
+ except RuntimeError:
275
+ return asyncio.run(coro)
276
+
277
+ if threading.current_thread() is threading.main_thread():
278
+ if not loop.is_running():
279
+ return loop.run_until_complete(coro)
280
+ else:
281
+ with ThreadPoolExecutor() as pool:
282
+ future = pool.submit(__run_in_new_loop, coro)
283
+ return future.result(timeout=30)
284
+ else:
285
+ return asyncio.run_coroutine_threadsafe(coro, loop).result()
286
+
287
+
288
+ def __run_in_new_loop(coroutine: Coroutine[Any, Any, Any]):
289
+ new_loop = asyncio.new_event_loop()
290
+ asyncio.set_event_loop(new_loop)
291
+ try:
292
+ return new_loop.run_until_complete(coroutine)
293
+ finally:
294
+ new_loop.close()
@@ -99,6 +99,7 @@ class Client(_BaseClient):
99
99
  return the entire response URI. This will substitute the interactive
100
100
  authentication code step in the terminal.
101
101
  :param on_token_refresh: A callback function that is called whenever the token is refreshed.
102
+ This includes the initial token retrieval and any subsequent calls.
102
103
  With this you can for example securely store the token in your
103
104
  application or on your server for later reuse. The function
104
105
  must accept a single argument, which is the token dictionary
@@ -145,10 +145,10 @@ class TaskSummary(AnaplanModel):
145
145
 
146
146
 
147
147
  class TaskResultDetail(AnaplanModel):
148
- local_message_text: str = Field(description="Error message text.")
148
+ local_message_text: str | None = Field(None, description="Error message text.")
149
149
  occurrences: int = Field(0, description="The number of occurrences of this error.")
150
150
  type: str = Field(description="The type of this error.")
151
- values: list[str] = Field([], description="Further error information if available.")
151
+ values: list[str | None] = Field([], description="Further error information if available.")
152
152
 
153
153
 
154
154
  class TaskResult(AnaplanModel):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anaplan-sdk
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Streamlined Python Interface for Anaplan
5
5
  Project-URL: Homepage, https://vinzenzklass.github.io/anaplan-sdk/
6
6
  Project-URL: Repository, https://github.com/VinzenzKlass/anaplan-sdk
@@ -1,29 +1,29 @@
1
1
  anaplan_sdk/__init__.py,sha256=5fr-SZSsH6f3vkRUTDoK6xdAN31cCpe9Mwz2VNu47Uw,134
2
- anaplan_sdk/_auth.py,sha256=LjPKGoM7qqiFmr9qC4_ZnV9sTHPbvhrnf6iSq_w1tNI,10569
2
+ anaplan_sdk/_auth.py,sha256=0htPrOYXDb2CCm4ZkwKQ4Zi26fsK6D0OIBiQdR6ESm8,11817
3
3
  anaplan_sdk/_base.py,sha256=9CdLshORWsLixOyoFa3A0Bka5lhLwlZrQI5sEdBcGFI,12298
4
4
  anaplan_sdk/exceptions.py,sha256=ALkA9fBF0NQ7dufFxV6AivjmHyuJk9DOQ9jtJV2n7f0,1809
5
5
  anaplan_sdk/_async_clients/__init__.py,sha256=pZXgMMg4S9Aj_pxQCaSiPuNG-sePVGBtNJ0133VjqW4,364
6
6
  anaplan_sdk/_async_clients/_alm.py,sha256=O1_r-O1tNDq7vXRwE2UEFE5S2bPmPh4IAQPQ8bmZfQE,3297
7
7
  anaplan_sdk/_async_clients/_audit.py,sha256=a92RY0B3bWxp2CCAWjzqKfvBjG1LJGlai0Hn5qmwgF8,2312
8
- anaplan_sdk/_async_clients/_bulk.py,sha256=YLttQd9oVHrvafXZ_J0QD8EQ3QCtmcT9YRZQ8_X71AI,25674
8
+ anaplan_sdk/_async_clients/_bulk.py,sha256=APhgKE4Deh90lm8rcCJMyQTJNMHAXFCKkqnGV_lAtgY,26908
9
9
  anaplan_sdk/_async_clients/_cloud_works.py,sha256=KPX9W55SF6h8fJd4Rx-HLq6eaRA-Vo3rFu343UiiaGQ,16642
10
10
  anaplan_sdk/_async_clients/_cw_flow.py,sha256=ZTNAbKDwb59Wg3u68hbtt1kpd-LNz9K0sftT-gvYzJQ,3651
11
11
  anaplan_sdk/_async_clients/_transactional.py,sha256=Mvr7OyBPjQRpBtzkJNfRzV4aNCzUiaYmm0zQubo62Wo,8035
12
12
  anaplan_sdk/_clients/__init__.py,sha256=FsbwvZC1FHrxuRXwbPxUzbhz_lO1DpXIxEOjx6-3QuA,219
13
13
  anaplan_sdk/_clients/_alm.py,sha256=UAdQxgHfax-VquC0YtbqrRBku2Rn35tVgwJdxYFScps,3202
14
14
  anaplan_sdk/_clients/_audit.py,sha256=xQQiwWIb4QQefolPvxNwBFE-pkRzzi8fYPyewjF63lc,2181
15
- anaplan_sdk/_clients/_bulk.py,sha256=TVinTtTYnlcoTrOX0ldpW11qui5rSUXm_PE1L7JgST8,25466
15
+ anaplan_sdk/_clients/_bulk.py,sha256=4JkuutqCo7yt3Ik2f90ixkfPw1r-7TOq9xg1MUZ5es8,25568
16
16
  anaplan_sdk/_clients/_cloud_works.py,sha256=KAMnLoeMJ2iwMXlDSbKynCE57BtkCfOgM5O8wT1kkSs,16291
17
17
  anaplan_sdk/_clients/_cw_flow.py,sha256=5IFWFT-qbyGvaSOOtaFOjHnOlyYbj4Rj3xiavfTlm8c,3527
18
18
  anaplan_sdk/_clients/_transactional.py,sha256=YUVbA54uhMloQcahwMtmZO3YooO6qQzwZN3ZRSu_z_c,7976
19
19
  anaplan_sdk/models/__init__.py,sha256=nSplwPG_74CG9CKbv1PzP9bsA9v5-daS4azpTCvCQTI,925
20
20
  anaplan_sdk/models/_alm.py,sha256=IqsTPvkx_ujLpaqZgIrTcr44KHJyKc4dyeRs9rkDjms,2307
21
21
  anaplan_sdk/models/_base.py,sha256=6AZc9CfireUKgpZfMxYKu4MbwiyHQOsGLjKrxGXBLic,508
22
- anaplan_sdk/models/_bulk.py,sha256=dHP3kMvsKONCZS6mHB271-wp2S4P3rM874Ita8TzABU,8256
22
+ anaplan_sdk/models/_bulk.py,sha256=_lHARGGjJgi-AmA7u5ZfCmGpLecPnr73LSAsZSX-a_A,8276
23
23
  anaplan_sdk/models/_transactional.py,sha256=_0UbVR9D5QABI29yloYrJTSgL-K0EU7PzPeJu5LdhnY,4854
24
24
  anaplan_sdk/models/cloud_works.py,sha256=nfn_LHPR-KmW7Tpvz-5qNCzmR8SYgvsVV-lx5iDlyqI,19425
25
25
  anaplan_sdk/models/flows.py,sha256=SuLgNj5-2SeE3U1i8iY8cq2IkjuUgd_3M1n2ENructk,3625
26
- anaplan_sdk-0.4.1.dist-info/METADATA,sha256=BBxu0ztPLPPxIJtueo3P3cfs1webd9Fxu60hFdHi0p8,3543
27
- anaplan_sdk-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
- anaplan_sdk-0.4.1.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
29
- anaplan_sdk-0.4.1.dist-info/RECORD,,
26
+ anaplan_sdk-0.4.3.dist-info/METADATA,sha256=sWnybkI9lnrdm5E84nUT03348APtgN8gaMdC4diqNTs,3543
27
+ anaplan_sdk-0.4.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
+ anaplan_sdk-0.4.3.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
29
+ anaplan_sdk-0.4.3.dist-info/RECORD,,