anaplan-sdk 0.4.1__py3-none-any.whl → 0.4.3a1__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/__init__.py +2 -1
- anaplan_sdk/_async_clients/_bulk.py +23 -6
- anaplan_sdk/_auth.py +77 -9
- anaplan_sdk/_clients/_bulk.py +7 -2
- anaplan_sdk/_oauth.py +198 -0
- {anaplan_sdk-0.4.1.dist-info → anaplan_sdk-0.4.3a1.dist-info}/METADATA +1 -1
- {anaplan_sdk-0.4.1.dist-info → anaplan_sdk-0.4.3a1.dist-info}/RECORD +9 -8
- {anaplan_sdk-0.4.1.dist-info → anaplan_sdk-0.4.3a1.dist-info}/WHEEL +0 -0
- {anaplan_sdk-0.4.1.dist-info → anaplan_sdk-0.4.3a1.dist-info}/licenses/LICENSE +0 -0
anaplan_sdk/__init__.py
CHANGED
@@ -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,
|
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
|
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,10 @@ 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:
|
60
|
-
on_token_refresh:
|
59
|
+
on_auth_code: AuthCodeCallback = None,
|
60
|
+
on_token_refresh: AuthTokenRefreshCallback = None,
|
61
|
+
oauth_token: dict[str, str] | None = None,
|
62
|
+
token: str | None = None,
|
61
63
|
timeout: float | httpx.Timeout = 30,
|
62
64
|
retry_count: int = 2,
|
63
65
|
status_poll_delay: int = 1,
|
@@ -94,12 +96,25 @@ class AsyncClient(_AsyncBaseClient):
|
|
94
96
|
:param oauth2_scope: The scope of the Oauth2 token, if you want to narrow it.
|
95
97
|
:param on_auth_code: A callback that takes the redirect URI as a single argument and must
|
96
98
|
return the entire response URI. This will substitute the interactive
|
97
|
-
authentication code step in the terminal.
|
99
|
+
authentication code step in the terminal. The callback can be either
|
100
|
+
a synchronous function or an async coroutine function - both will be
|
101
|
+
handled appropriately regardless of the execution context (in a thread,
|
102
|
+
with or without an event loop, etc.).
|
103
|
+
**Note**: When using asynchronous callbacks in complex applications
|
104
|
+
with multiple event loops, be aware that callbacks may execute in a
|
105
|
+
separate event loop context from where they were defined, which can
|
106
|
+
make debugging challenging.
|
98
107
|
:param on_token_refresh: A callback function that is called whenever the token is refreshed.
|
108
|
+
This includes the initial token retrieval and any subsequent calls.
|
99
109
|
With this you can for example securely store the token in your
|
100
110
|
application or on your server for later reuse. The function
|
101
111
|
must accept a single argument, which is the token dictionary
|
102
112
|
returned by the Oauth2 token endpoint and does not return anything.
|
113
|
+
This can be either a synchronous function or an async coroutine
|
114
|
+
function. **Note**: When using asynchronous callbacks in complex
|
115
|
+
applications with multiple event loops, be aware that callbacks
|
116
|
+
may execute in a separate event loop context from where they were
|
117
|
+
defined, which can make debugging challenging.
|
103
118
|
:param timeout: The timeout in seconds for the HTTP requests. Alternatively, you can pass
|
104
119
|
an instance of `httpx.Timeout` to set the timeout for the HTTP requests.
|
105
120
|
:param retry_count: The number of times to retry an HTTP request if it fails. Set this to 0
|
@@ -117,7 +132,9 @@ class AsyncClient(_AsyncBaseClient):
|
|
117
132
|
"""
|
118
133
|
_client = httpx.AsyncClient(
|
119
134
|
auth=(
|
120
|
-
|
135
|
+
_create_auth(
|
136
|
+
token=token,
|
137
|
+
oauth_token=oauth_token,
|
121
138
|
user_email=user_email,
|
122
139
|
password=password,
|
123
140
|
certificate=certificate,
|
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
|
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
|
@@ -41,6 +50,18 @@ class _AnaplanAuth(httpx.Auth):
|
|
41
50
|
self._token: str = response.json()["tokenInfo"]["tokenValue"]
|
42
51
|
|
43
52
|
|
53
|
+
class StaticTokenAuth(httpx.Auth):
|
54
|
+
def __init__(self, token: str):
|
55
|
+
logger.warning("Using static token authentication. Tokens will not be refreshed.")
|
56
|
+
self._token = token
|
57
|
+
|
58
|
+
def auth_flow(self, request):
|
59
|
+
request.headers["Authorization"] = f"AnaplanAuthToken {self._token}"
|
60
|
+
response = yield request
|
61
|
+
if response.status_code == 401:
|
62
|
+
raise InvalidCredentialsException("Your token is invalid or expired.")
|
63
|
+
|
64
|
+
|
44
65
|
class AnaplanBasicAuth(_AnaplanAuth):
|
45
66
|
def __init__(self, user_email: str, password: str):
|
46
67
|
self.user_email = user_email
|
@@ -144,8 +165,8 @@ class AnaplanOauth2AuthCodeAuth(_AnaplanAuth):
|
|
144
165
|
redirect_uri: str,
|
145
166
|
refresh_token: str | None = None,
|
146
167
|
scope: str = "openid profile email offline_access",
|
147
|
-
on_auth_code:
|
148
|
-
on_token_refresh:
|
168
|
+
on_auth_code: AuthCodeCallback = None,
|
169
|
+
on_token_refresh: AuthTokenRefreshCallback = None,
|
149
170
|
):
|
150
171
|
try:
|
151
172
|
from oauthlib.oauth2 import WebApplicationClient
|
@@ -188,7 +209,7 @@ class AnaplanOauth2AuthCodeAuth(_AnaplanAuth):
|
|
188
209
|
self._token = token["access_token"]
|
189
210
|
self._refresh_token = token["refresh_token"]
|
190
211
|
if self._on_token_refresh:
|
191
|
-
self._on_token_refresh
|
212
|
+
_run_callback(self._on_token_refresh, token)
|
192
213
|
self._id_token = token.get("id_token")
|
193
214
|
|
194
215
|
def __auth_code_flow(self):
|
@@ -202,7 +223,7 @@ class AnaplanOauth2AuthCodeAuth(_AnaplanAuth):
|
|
202
223
|
scope=self._scope,
|
203
224
|
)
|
204
225
|
authorization_response = (
|
205
|
-
self._on_auth_code
|
226
|
+
_run_callback(self._on_auth_code, url)
|
206
227
|
if self._on_auth_code
|
207
228
|
else input(
|
208
229
|
f"Please go to {url} and authorize the app.\n"
|
@@ -220,7 +241,7 @@ class AnaplanOauth2AuthCodeAuth(_AnaplanAuth):
|
|
220
241
|
raise InvalidCredentialsException("Error during OAuth2 authorization flow.") from error
|
221
242
|
|
222
243
|
|
223
|
-
def
|
244
|
+
def _create_auth(
|
224
245
|
user_email: str | None = None,
|
225
246
|
password: str | None = None,
|
226
247
|
certificate: str | bytes | None = None,
|
@@ -231,9 +252,27 @@ def create_auth(
|
|
231
252
|
redirect_uri: str | None = None,
|
232
253
|
refresh_token: str | None = None,
|
233
254
|
oauth2_scope: str = "openid profile email offline_access",
|
234
|
-
on_auth_code:
|
235
|
-
on_token_refresh:
|
236
|
-
|
255
|
+
on_auth_code: AuthCodeCallback = None,
|
256
|
+
on_token_refresh: AuthTokenRefreshCallback = None,
|
257
|
+
token: str | None = None,
|
258
|
+
oauth_token: dict[str, str] | None = None,
|
259
|
+
) -> httpx.Auth:
|
260
|
+
if token:
|
261
|
+
# TODO: If other parameters are provided that allow refreshing the token, use them to create
|
262
|
+
# use them to create one of the other auth classes instead.
|
263
|
+
return StaticTokenAuth(token)
|
264
|
+
if oauth_token:
|
265
|
+
if not isinstance(oauth_token, dict) and all(
|
266
|
+
f in oauth_token for f in ("access_token", "refresh_token", "token_type", "scope")
|
267
|
+
):
|
268
|
+
raise ValueError(
|
269
|
+
"oauth_token must be a dictionary with at least 'access_token', 'refresh_token', "
|
270
|
+
"'token_type', and 'scope' keys."
|
271
|
+
)
|
272
|
+
# TODO: If client_id, client_secret, and redirect_uri are provided, use them to create an
|
273
|
+
# AnaplanOauth2AuthCodeAuth (extend to accept existing `access_token` instance. Else, use
|
274
|
+
# the StaticTokenAuth directly.
|
275
|
+
return StaticTokenAuth(oauth_token["access_token"])
|
237
276
|
if certificate and private_key:
|
238
277
|
return AnaplanCertAuth(certificate, private_key, private_key_password)
|
239
278
|
if user_email and password:
|
@@ -254,3 +293,32 @@ def create_auth(
|
|
254
293
|
"- certificate and private_key, or\n"
|
255
294
|
"- client_id, client_secret, and redirect_uri"
|
256
295
|
)
|
296
|
+
|
297
|
+
|
298
|
+
def _run_callback(func, *arg, **kwargs):
|
299
|
+
if not inspect.iscoroutinefunction(func):
|
300
|
+
return func(*arg, **kwargs)
|
301
|
+
coro = func(*arg, **kwargs)
|
302
|
+
try:
|
303
|
+
loop = asyncio.get_running_loop()
|
304
|
+
except RuntimeError:
|
305
|
+
return asyncio.run(coro)
|
306
|
+
|
307
|
+
if threading.current_thread() is threading.main_thread():
|
308
|
+
if not loop.is_running():
|
309
|
+
return loop.run_until_complete(coro)
|
310
|
+
else:
|
311
|
+
with ThreadPoolExecutor() as pool:
|
312
|
+
future = pool.submit(__run_in_new_loop, coro)
|
313
|
+
return future.result(timeout=30)
|
314
|
+
else:
|
315
|
+
return asyncio.run_coroutine_threadsafe(coro, loop).result()
|
316
|
+
|
317
|
+
|
318
|
+
def __run_in_new_loop(coroutine: Coroutine[Any, Any, Any]):
|
319
|
+
new_loop = asyncio.new_event_loop()
|
320
|
+
asyncio.set_event_loop(new_loop)
|
321
|
+
try:
|
322
|
+
return new_loop.run_until_complete(coroutine)
|
323
|
+
finally:
|
324
|
+
new_loop.close()
|
anaplan_sdk/_clients/_bulk.py
CHANGED
@@ -8,7 +8,7 @@ from typing import Callable, Iterator
|
|
8
8
|
import httpx
|
9
9
|
from typing_extensions import Self
|
10
10
|
|
11
|
-
from anaplan_sdk._auth import
|
11
|
+
from anaplan_sdk._auth import _create_auth
|
12
12
|
from anaplan_sdk._base import _BaseClient, action_url
|
13
13
|
from anaplan_sdk.exceptions import AnaplanActionError, InvalidIdentifierException
|
14
14
|
from anaplan_sdk.models import (
|
@@ -60,6 +60,8 @@ class Client(_BaseClient):
|
|
60
60
|
oauth2_scope: str = "openid profile email offline_access",
|
61
61
|
on_auth_code: Callable[[str], str] | None = None,
|
62
62
|
on_token_refresh: Callable[[dict[str, str]], None] | None = None,
|
63
|
+
oauth_token: dict[str, str] | None = None,
|
64
|
+
token: str | None = None,
|
63
65
|
timeout: float | httpx.Timeout = 30,
|
64
66
|
retry_count: int = 2,
|
65
67
|
status_poll_delay: int = 1,
|
@@ -99,6 +101,7 @@ class Client(_BaseClient):
|
|
99
101
|
return the entire response URI. This will substitute the interactive
|
100
102
|
authentication code step in the terminal.
|
101
103
|
:param on_token_refresh: A callback function that is called whenever the token is refreshed.
|
104
|
+
This includes the initial token retrieval and any subsequent calls.
|
102
105
|
With this you can for example securely store the token in your
|
103
106
|
application or on your server for later reuse. The function
|
104
107
|
must accept a single argument, which is the token dictionary
|
@@ -123,7 +126,9 @@ class Client(_BaseClient):
|
|
123
126
|
"""
|
124
127
|
_client = httpx.Client(
|
125
128
|
auth=(
|
126
|
-
|
129
|
+
_create_auth(
|
130
|
+
token=token,
|
131
|
+
oauth_token=oauth_token,
|
127
132
|
user_email=user_email,
|
128
133
|
password=password,
|
129
134
|
certificate=certificate,
|
anaplan_sdk/_oauth.py
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Callable
|
3
|
+
|
4
|
+
import httpx
|
5
|
+
|
6
|
+
from .exceptions import AnaplanException, InvalidCredentialsException
|
7
|
+
|
8
|
+
logger = logging.getLogger("anaplan_sdk")
|
9
|
+
|
10
|
+
|
11
|
+
class _BaseOauth:
|
12
|
+
def __init__(
|
13
|
+
self,
|
14
|
+
client_id: str,
|
15
|
+
client_secret: str,
|
16
|
+
redirect_url: str,
|
17
|
+
authorization_url: str = "https://us1a.app.anaplan.com/auth/prelogin",
|
18
|
+
token_url: str = "https://us1a.app.anaplan.com/oauth/token",
|
19
|
+
validation_url: str = "https://auth.anaplan.com/token/validate",
|
20
|
+
scope: str = "openid profile email offline_access",
|
21
|
+
state_generator: Callable[[], str] | None = None,
|
22
|
+
):
|
23
|
+
"""
|
24
|
+
Initializes the OAuth Client. This class provides the two utilities needed to implement
|
25
|
+
the OAuth 2.0 authorization code flow for user-facing Web Applications. It differs from the
|
26
|
+
other Authentication Strategies in this SDK in two main ways:
|
27
|
+
|
28
|
+
1. You must implement the actual authentication flow in your application. You cannot pass
|
29
|
+
the credentials directly to the `Client` or `AsyncClient`, and this class does not
|
30
|
+
implement the SDK internal authentication flow, i.e. it does not subclass `httpx.Auth`.
|
31
|
+
|
32
|
+
2. You then simply pass the resulting token to the `Client` or `AsyncClient`, rather than
|
33
|
+
passing the credentials directly, which will internally construct an `httpx.Auth` instance
|
34
|
+
|
35
|
+
Note that this class exist for convenience only, and you can implement the OAuth 2.0 Flow
|
36
|
+
yourself in your preferred library, or bring an existing implementation. For details on the
|
37
|
+
Anaplan OAuth 2.0 Flow, see the [the Docs](https://anaplanoauth2service.docs.apiary.io/#reference/overview-of-the-authorization-code-grant).
|
38
|
+
:param client_id: The client ID of your Anaplan Oauth 2.0 application. This Application
|
39
|
+
must be an Authorization Code Grant application.
|
40
|
+
:param client_secret: The client secret of your Anaplan Oauth 2.0 application.
|
41
|
+
:param redirect_url: The URL to which the user will be redirected after authorizing the
|
42
|
+
application.
|
43
|
+
:param authorization_url: The URL to which the user will be redirected to authorize the
|
44
|
+
application. Defaults to the Anaplan Prelogin Page, where the user can select the
|
45
|
+
login method.
|
46
|
+
:param token_url: The URL to post the authorization code to in order to fetch the access
|
47
|
+
token.
|
48
|
+
:param validation_url: The URL to validate the access token.
|
49
|
+
:param scope: The scope of the access request.
|
50
|
+
:param state_generator: A callable that generates a random state string. You can optionally
|
51
|
+
pass this if you need to customize the state generation logic. If not provided,
|
52
|
+
the state will be generated by `oauthlib`.
|
53
|
+
"""
|
54
|
+
self._client_id = client_id
|
55
|
+
self._client_secret = client_secret
|
56
|
+
self._redirect_url = redirect_url
|
57
|
+
self._authorization_url = authorization_url
|
58
|
+
self._token_url = token_url
|
59
|
+
self._validation_url = validation_url
|
60
|
+
self._scope = scope
|
61
|
+
|
62
|
+
try:
|
63
|
+
from oauthlib.oauth2 import WebApplicationClient
|
64
|
+
except ImportError as e:
|
65
|
+
raise AnaplanException(
|
66
|
+
"oauthlib is not available. Please install anaplan-sdk with the oauth extra "
|
67
|
+
"`pip install anaplan-sdk[oauth]` or install oauthlib separately."
|
68
|
+
) from e
|
69
|
+
self._oauth = WebApplicationClient(client_id=client_id, client_secret=client_secret)
|
70
|
+
self._state_generator = state_generator if state_generator else self._oauth.state_generator
|
71
|
+
|
72
|
+
def authorization_url(
|
73
|
+
self, authorization_url: str | None = None, state: str | None = None
|
74
|
+
) -> tuple[str, str]:
|
75
|
+
"""
|
76
|
+
Generates the authorization URL for the OAuth 2.0 flow.
|
77
|
+
:param authorization_url: You can optionally pass a custom authorization URL. This is
|
78
|
+
useful if you want to redirect i.e. redirect the user directly to the Anaplan login
|
79
|
+
page rather than the Prelogin page in only one scenario, while still reusing the
|
80
|
+
Client.
|
81
|
+
:param state: You can optionally pass a custom state string. If not provided, a random
|
82
|
+
state string will be generated by the `oauthlib` library, or by the
|
83
|
+
`state_generator` callable if provided.
|
84
|
+
:return: A tuple containing the authorization URL and the state string.
|
85
|
+
"""
|
86
|
+
auth_url = authorization_url or self._authorization_url
|
87
|
+
state = state or self._state_generator()
|
88
|
+
url, _, _ = self._oauth.prepare_authorization_request(
|
89
|
+
auth_url, state, self._redirect_url, self._scope
|
90
|
+
)
|
91
|
+
return url, state
|
92
|
+
|
93
|
+
|
94
|
+
class AsyncOauth(_BaseOauth):
|
95
|
+
"""
|
96
|
+
Asynchronous Variant of the Anaplan OAuth client for interactive OAuth Flows in Web
|
97
|
+
Applications.
|
98
|
+
"""
|
99
|
+
|
100
|
+
async def fetch_token(self, authorization_response: str) -> dict[str, str]:
|
101
|
+
"""
|
102
|
+
Fetches the token using the authorization response from the OAuth 2.0 flow.
|
103
|
+
:param authorization_response: The full URL that the user was redirected to after
|
104
|
+
authorizing the application. This URL will contain the authorization code and state.
|
105
|
+
:return: The token as a dictionary containing the access token, refresh token, scope,
|
106
|
+
expires_in, and type.
|
107
|
+
"""
|
108
|
+
from oauthlib.oauth2 import OAuth2Error
|
109
|
+
|
110
|
+
try:
|
111
|
+
url, headers, body = self._oauth.prepare_token_request(
|
112
|
+
authorization_response=authorization_response,
|
113
|
+
token_url=self._token_url,
|
114
|
+
redirect_url=self._redirect_url,
|
115
|
+
client_secret=self._client_secret,
|
116
|
+
)
|
117
|
+
async with httpx.AsyncClient() as client:
|
118
|
+
response = await client.post(url=url, headers=headers, content=body)
|
119
|
+
if not response.is_success:
|
120
|
+
raise InvalidCredentialsException(
|
121
|
+
f"Token request failed: {response.status_code} {response.text}"
|
122
|
+
)
|
123
|
+
return response.json()
|
124
|
+
except (httpx.HTTPError, ValueError, TypeError, OAuth2Error) as error:
|
125
|
+
logger.error(error)
|
126
|
+
raise AnaplanException("Error during token creation.") from error
|
127
|
+
|
128
|
+
async def validate_token(self, token: str) -> dict[str, str | dict[str, str]]:
|
129
|
+
"""
|
130
|
+
Validates the provided token by checking its validity with the Anaplan Authentication API.
|
131
|
+
If the token is not valid, an `InvalidCredentialsException` is raised.
|
132
|
+
:param token: The access token to validate.
|
133
|
+
:return: The Token information as a dictionary containing the token's details.
|
134
|
+
"""
|
135
|
+
try:
|
136
|
+
async with httpx.AsyncClient() as client:
|
137
|
+
response = await client.get(
|
138
|
+
url=self._validation_url, headers={"Authorization": f"AnaplanAuthToken {token}"}
|
139
|
+
)
|
140
|
+
if not response.is_success:
|
141
|
+
raise InvalidCredentialsException
|
142
|
+
return response.json()
|
143
|
+
except httpx.HTTPError as error:
|
144
|
+
logger.error(error)
|
145
|
+
raise AnaplanException("Error during token validation.") from error
|
146
|
+
|
147
|
+
|
148
|
+
class Oauth(_BaseOauth):
|
149
|
+
"""
|
150
|
+
Synchronous Variant of the Anaplan OAuth client for interactive OAuth Flows in Web
|
151
|
+
Applications.
|
152
|
+
"""
|
153
|
+
|
154
|
+
def fetch_token(self, authorization_response: str) -> dict[str, str]:
|
155
|
+
"""
|
156
|
+
Fetches the token using the authorization response from the OAuth 2.0 flow.
|
157
|
+
:param authorization_response: The full URL that the user was redirected to after
|
158
|
+
authorizing the application. This URL will contain the authorization code and state.
|
159
|
+
:return: The token as a dictionary containing the access token, refresh token, scope,
|
160
|
+
expires_in, and type.
|
161
|
+
"""
|
162
|
+
from oauthlib.oauth2 import OAuth2Error
|
163
|
+
|
164
|
+
try:
|
165
|
+
url, headers, body = self._oauth.prepare_token_request(
|
166
|
+
authorization_response=authorization_response,
|
167
|
+
token_url=self._token_url,
|
168
|
+
redirect_url=self._redirect_url,
|
169
|
+
client_secret=self._client_secret,
|
170
|
+
)
|
171
|
+
with httpx.Client() as client:
|
172
|
+
response = client.post(url=url, headers=headers, content=body)
|
173
|
+
if not response.is_success:
|
174
|
+
raise AnaplanException(
|
175
|
+
f"Token request failed: {response.status_code} {response.text}"
|
176
|
+
)
|
177
|
+
return response.json()
|
178
|
+
except (httpx.HTTPError, ValueError, TypeError, OAuth2Error) as error:
|
179
|
+
raise AnaplanException("Error during token creation.") from error
|
180
|
+
|
181
|
+
def validate_token(self, token: str) -> dict[str, str | dict[str, str]]:
|
182
|
+
"""
|
183
|
+
Validates the provided token by checking its validity with the Anaplan Authentication API.
|
184
|
+
If the token is not valid, an `InvalidCredentialsException` is raised.
|
185
|
+
:param token: The access token to validate.
|
186
|
+
:return: The Token information as a dictionary containing the token's details.
|
187
|
+
"""
|
188
|
+
try:
|
189
|
+
with httpx.Client() as client:
|
190
|
+
response = client.get(
|
191
|
+
url=self._validation_url, headers={"Authorization": f"AnaplanAuthToken {token}"}
|
192
|
+
)
|
193
|
+
if not response.is_success:
|
194
|
+
raise InvalidCredentialsException
|
195
|
+
return response.json()
|
196
|
+
except httpx.HTTPError as error:
|
197
|
+
logger.error(error)
|
198
|
+
raise AnaplanException("Error during token validation.") from error
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: anaplan-sdk
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.3a1
|
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,18 +1,19 @@
|
|
1
|
-
anaplan_sdk/__init__.py,sha256=
|
2
|
-
anaplan_sdk/_auth.py,sha256=
|
1
|
+
anaplan_sdk/__init__.py,sha256=4P-BZnoTxQdKRHOH0DUe-B4XUQKPrK45UfMDWeb1TtI,196
|
2
|
+
anaplan_sdk/_auth.py,sha256=wo0iuQm1-uZiZ081hA2Y2knc-w1RIwhea_6CEqDxPto,13276
|
3
3
|
anaplan_sdk/_base.py,sha256=9CdLshORWsLixOyoFa3A0Bka5lhLwlZrQI5sEdBcGFI,12298
|
4
|
+
anaplan_sdk/_oauth.py,sha256=TDv4d10htjhUIbcGrvwTf4Z7dYWoeh18Wl5CiMu93fE,9914
|
4
5
|
anaplan_sdk/exceptions.py,sha256=ALkA9fBF0NQ7dufFxV6AivjmHyuJk9DOQ9jtJV2n7f0,1809
|
5
6
|
anaplan_sdk/_async_clients/__init__.py,sha256=pZXgMMg4S9Aj_pxQCaSiPuNG-sePVGBtNJ0133VjqW4,364
|
6
7
|
anaplan_sdk/_async_clients/_alm.py,sha256=O1_r-O1tNDq7vXRwE2UEFE5S2bPmPh4IAQPQ8bmZfQE,3297
|
7
8
|
anaplan_sdk/_async_clients/_audit.py,sha256=a92RY0B3bWxp2CCAWjzqKfvBjG1LJGlai0Hn5qmwgF8,2312
|
8
|
-
anaplan_sdk/_async_clients/_bulk.py,sha256=
|
9
|
+
anaplan_sdk/_async_clients/_bulk.py,sha256=SYYqfiZ12FEETQP2lakEU3bGfy0mEenA34Nj3dwXkLc,27077
|
9
10
|
anaplan_sdk/_async_clients/_cloud_works.py,sha256=KPX9W55SF6h8fJd4Rx-HLq6eaRA-Vo3rFu343UiiaGQ,16642
|
10
11
|
anaplan_sdk/_async_clients/_cw_flow.py,sha256=ZTNAbKDwb59Wg3u68hbtt1kpd-LNz9K0sftT-gvYzJQ,3651
|
11
12
|
anaplan_sdk/_async_clients/_transactional.py,sha256=Mvr7OyBPjQRpBtzkJNfRzV4aNCzUiaYmm0zQubo62Wo,8035
|
12
13
|
anaplan_sdk/_clients/__init__.py,sha256=FsbwvZC1FHrxuRXwbPxUzbhz_lO1DpXIxEOjx6-3QuA,219
|
13
14
|
anaplan_sdk/_clients/_alm.py,sha256=UAdQxgHfax-VquC0YtbqrRBku2Rn35tVgwJdxYFScps,3202
|
14
15
|
anaplan_sdk/_clients/_audit.py,sha256=xQQiwWIb4QQefolPvxNwBFE-pkRzzi8fYPyewjF63lc,2181
|
15
|
-
anaplan_sdk/_clients/_bulk.py,sha256=
|
16
|
+
anaplan_sdk/_clients/_bulk.py,sha256=RCFczu_iePjn6O_1Oj4m8lMtaSw7bwNHIVHMz_ZsYPU,25737
|
16
17
|
anaplan_sdk/_clients/_cloud_works.py,sha256=KAMnLoeMJ2iwMXlDSbKynCE57BtkCfOgM5O8wT1kkSs,16291
|
17
18
|
anaplan_sdk/_clients/_cw_flow.py,sha256=5IFWFT-qbyGvaSOOtaFOjHnOlyYbj4Rj3xiavfTlm8c,3527
|
18
19
|
anaplan_sdk/_clients/_transactional.py,sha256=YUVbA54uhMloQcahwMtmZO3YooO6qQzwZN3ZRSu_z_c,7976
|
@@ -23,7 +24,7 @@ anaplan_sdk/models/_bulk.py,sha256=dHP3kMvsKONCZS6mHB271-wp2S4P3rM874Ita8TzABU,8
|
|
23
24
|
anaplan_sdk/models/_transactional.py,sha256=_0UbVR9D5QABI29yloYrJTSgL-K0EU7PzPeJu5LdhnY,4854
|
24
25
|
anaplan_sdk/models/cloud_works.py,sha256=nfn_LHPR-KmW7Tpvz-5qNCzmR8SYgvsVV-lx5iDlyqI,19425
|
25
26
|
anaplan_sdk/models/flows.py,sha256=SuLgNj5-2SeE3U1i8iY8cq2IkjuUgd_3M1n2ENructk,3625
|
26
|
-
anaplan_sdk-0.4.
|
27
|
-
anaplan_sdk-0.4.
|
28
|
-
anaplan_sdk-0.4.
|
29
|
-
anaplan_sdk-0.4.
|
27
|
+
anaplan_sdk-0.4.3a1.dist-info/METADATA,sha256=mxfCoUiocn3SPGKbUbUnHukS9eyRYj12EVMZIMFjI7k,3545
|
28
|
+
anaplan_sdk-0.4.3a1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
29
|
+
anaplan_sdk-0.4.3a1.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
30
|
+
anaplan_sdk-0.4.3a1.dist-info/RECORD,,
|
File without changes
|
File without changes
|