anaplan-sdk 0.4.4a3__py3-none-any.whl → 0.4.4a4__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 -2
- anaplan_sdk/_auth.py +78 -14
- anaplan_sdk/models/_bulk.py +2 -2
- {anaplan_sdk-0.4.4a3.dist-info → anaplan_sdk-0.4.4a4.dist-info}/METADATA +3 -1
- {anaplan_sdk-0.4.4a3.dist-info → anaplan_sdk-0.4.4a4.dist-info}/RECORD +7 -7
- {anaplan_sdk-0.4.4a3.dist-info → anaplan_sdk-0.4.4a4.dist-info}/WHEEL +0 -0
- {anaplan_sdk-0.4.4a3.dist-info → anaplan_sdk-0.4.4a4.dist-info}/licenses/LICENSE +0 -0
anaplan_sdk/__init__.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
from ._async_clients import AsyncClient
|
2
|
-
from ._auth import
|
2
|
+
from ._auth import AnaplanLocalOAuth, AnaplanRefreshTokenAuth
|
3
3
|
from ._clients import Client
|
4
4
|
from ._oauth import AsyncOauth, Oauth
|
5
5
|
|
6
6
|
__all__ = [
|
7
7
|
"AsyncClient",
|
8
8
|
"Client",
|
9
|
-
"
|
9
|
+
"AnaplanLocalOAuth",
|
10
10
|
"AnaplanRefreshTokenAuth",
|
11
11
|
"AsyncOauth",
|
12
12
|
"Oauth",
|
anaplan_sdk/_auth.py
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
from base64 import b64encode
|
4
|
-
from typing import
|
4
|
+
from typing import Callable
|
5
5
|
|
6
6
|
import httpx
|
7
|
+
import keyring
|
7
8
|
|
8
9
|
from ._oauth import _OAuthRequestFactory
|
9
10
|
from .exceptions import AnaplanException, InvalidCredentialsException, InvalidPrivateKeyException
|
10
11
|
|
11
12
|
logger = logging.getLogger("anaplan_sdk")
|
12
13
|
|
13
|
-
AuthCodeCallback = (Callable[[str], str] | Callable[[str], Awaitable[str]]) | None
|
14
|
-
AuthTokenRefreshCallback = (
|
15
|
-
Callable[[dict[str, str]], None] | Callable[[dict[str, str]], Awaitable[None]]
|
16
|
-
) | None
|
17
|
-
|
18
14
|
|
19
15
|
class _AnaplanAuth(httpx.Auth):
|
20
16
|
requires_response_body = True
|
@@ -154,13 +150,14 @@ class _AnaplanCertAuth(_AnaplanAuth):
|
|
154
150
|
raise InvalidPrivateKeyException from error
|
155
151
|
|
156
152
|
|
157
|
-
class
|
153
|
+
class AnaplanLocalOAuth(_AnaplanAuth):
|
158
154
|
def __init__(
|
159
155
|
self,
|
160
156
|
client_id: str,
|
161
157
|
client_secret: str,
|
162
158
|
redirect_uri: str,
|
163
159
|
token: dict[str, str] | None = None,
|
160
|
+
persist_token: bool = False,
|
164
161
|
authorization_url: str = "https://us1a.app.anaplan.com/auth/prelogin",
|
165
162
|
token_url: str = "https://us1a.app.anaplan.com/oauth/token",
|
166
163
|
validation_url: str = "https://auth.anaplan.com/token/validate",
|
@@ -168,7 +165,7 @@ class AnaplanOAuthCodeAuth(_AnaplanAuth):
|
|
168
165
|
state_generator: Callable[[], str] | None = None,
|
169
166
|
):
|
170
167
|
"""
|
171
|
-
Initializes the
|
168
|
+
Initializes the AnaplanLocalOAuth class for OAuth2 authentication using the
|
172
169
|
Authorization Code Flow. This is a utility class for local development and requires user
|
173
170
|
interaction. For Web Applications and other scenarios, refer to `Oauth` or `AsyncOauth`.
|
174
171
|
This class will refresh the access token automatically when it expires.
|
@@ -177,6 +174,15 @@ class AnaplanOAuthCodeAuth(_AnaplanAuth):
|
|
177
174
|
:param client_secret: The client secret of your Anaplan Oauth 2.0 application.
|
178
175
|
:param redirect_uri: The URL to which the user will be redirected after authorizing the
|
179
176
|
application.
|
177
|
+
:param token: The OAuth token dictionary containing at least the `access_token` and
|
178
|
+
`refresh_token`. If not provided, the user will be prompted to interactive
|
179
|
+
authorize the application, if `persist_token` is set to False or no valid refresh
|
180
|
+
token is found in the keyring.
|
181
|
+
:param persist_token: If set to True, the refresh token will be stored in the system's
|
182
|
+
keyring, allowing the application to use the same refresh token across multiple
|
183
|
+
runs. If set to False, the user will be prompted to authorize the application each
|
184
|
+
time. This requires the `keyring` extra to be installed. If a valid refresh token
|
185
|
+
is found in the keyring, it will be used instead of the given `token` parameter.
|
180
186
|
:param authorization_url: The URL to which the user will be redirected to authorize the
|
181
187
|
application. Defaults to the Anaplan Prelogin Page, where the user can select the
|
182
188
|
login method.
|
@@ -185,11 +191,27 @@ class AnaplanOAuthCodeAuth(_AnaplanAuth):
|
|
185
191
|
:param validation_url: The URL to validate the access token.
|
186
192
|
:param scope: The scope of the access request.
|
187
193
|
:param state_generator: A callable that generates a random state string. You can optionally
|
188
|
-
|
189
|
-
|
194
|
+
pass this if you need to customize the state generation logic. If not provided,
|
195
|
+
the state will be generated by `oauthlib`.
|
190
196
|
"""
|
191
|
-
|
192
197
|
self._oauth_token = token or {}
|
198
|
+
self._service_name = "anaplan_sdk"
|
199
|
+
|
200
|
+
if persist_token:
|
201
|
+
try:
|
202
|
+
import keyring
|
203
|
+
|
204
|
+
stored = keyring.get_password(self._service_name, self._service_name)
|
205
|
+
if stored:
|
206
|
+
logger.info("Using persisted OAuth refresh token.")
|
207
|
+
self._oauth_token = {"refresh_token": stored}
|
208
|
+
self._token = None # Set to None to trigger the super().__init__ auth request.
|
209
|
+
except ImportError as e:
|
210
|
+
raise AnaplanException(
|
211
|
+
"keyring is not available. Please install anaplan-sdk with the keyring extra "
|
212
|
+
"`pip install anaplan-sdk[keyring]` or install keyring separately."
|
213
|
+
) from e
|
214
|
+
self._persist_token = persist_token
|
193
215
|
self._oauth = _OAuthRequestFactory(
|
194
216
|
client_id=client_id,
|
195
217
|
client_secret=client_secret,
|
@@ -200,10 +222,20 @@ class AnaplanOAuthCodeAuth(_AnaplanAuth):
|
|
200
222
|
validation_url=validation_url,
|
201
223
|
state_generator=state_generator,
|
202
224
|
)
|
203
|
-
if not
|
225
|
+
if not self._oauth_token:
|
204
226
|
self.__auth_code_flow()
|
205
227
|
super().__init__(self._token)
|
206
228
|
|
229
|
+
@property
|
230
|
+
def token(self) -> dict[str, str]:
|
231
|
+
"""
|
232
|
+
Returns the current token dictionary. You can safely use the `access_token`, but if you
|
233
|
+
must not use the `refresh_token` outside of this class, if you expect to use this instance
|
234
|
+
further. If you do use the `refresh_token` outside of this class, this will error on the
|
235
|
+
next attempt to refresh the token, as the `refresh_token` can only be used once.
|
236
|
+
"""
|
237
|
+
return self._oauth_token
|
238
|
+
|
207
239
|
def _build_auth_request(self) -> httpx.Request:
|
208
240
|
return self._oauth.refresh_token_request(self._oauth_token["refresh_token"])
|
209
241
|
|
@@ -213,6 +245,10 @@ class AnaplanOAuthCodeAuth(_AnaplanAuth):
|
|
213
245
|
if not response.is_success:
|
214
246
|
raise AnaplanException(f"Authentication failed: {response.status_code} {response.text}")
|
215
247
|
self._oauth_token = response.json()
|
248
|
+
if self._persist_token:
|
249
|
+
keyring.set_password(
|
250
|
+
self._service_name, self._service_name, self._oauth_token["refresh_token"]
|
251
|
+
)
|
216
252
|
self._token: str = self._oauth_token["access_token"]
|
217
253
|
|
218
254
|
def __auth_code_flow(self):
|
@@ -243,8 +279,20 @@ class AnaplanRefreshTokenAuth(_AnaplanAuth):
|
|
243
279
|
):
|
244
280
|
"""
|
245
281
|
This class is a utility class for long-lived `Client` or `AsyncClient` instances that use
|
246
|
-
OAuth.
|
247
|
-
to refresh the
|
282
|
+
OAuth. This class will use the `access_token` until the first request fails with a 401
|
283
|
+
Unauthorized error, at which point it will attempt to refresh the `access_token` using the
|
284
|
+
`refresh_token`. If the refresh fails, it will raise an `InvalidCredentialsException`. The
|
285
|
+
`expires_in` field in the token dictionary is not considered. Manipulating any of the
|
286
|
+
fields in the token dictionary is not recommended and will likely have no effect.
|
287
|
+
|
288
|
+
**For its entire lifetime, you are ceding control of the token to this class.**
|
289
|
+
You must not use the same token simultaneously in multiple instances of this class or
|
290
|
+
outside of it, as this may lead to unexpected behavior when e.g. the refresh token is
|
291
|
+
used, which can only happen once and will lead to errors when attempting to use the same
|
292
|
+
refresh token again elsewhere.
|
293
|
+
|
294
|
+
If you need the token back before this instance is destroyed, you can use the `token`
|
295
|
+
method.
|
248
296
|
|
249
297
|
:param client_id: The client ID of your Anaplan Oauth 2.0 application. This Application
|
250
298
|
must be an Authorization Code Grant application.
|
@@ -256,6 +304,12 @@ class AnaplanRefreshTokenAuth(_AnaplanAuth):
|
|
256
304
|
:param token_url: The URL to post the refresh token request to in order to fetch the access
|
257
305
|
token.
|
258
306
|
"""
|
307
|
+
if not isinstance(token, dict) and all(
|
308
|
+
key in token for key in ("access_token", "refresh_token")
|
309
|
+
):
|
310
|
+
raise ValueError(
|
311
|
+
"The token must at least contain 'access_token' and 'refresh_token' keys."
|
312
|
+
)
|
259
313
|
self._oauth_token = token
|
260
314
|
self._oauth = _OAuthRequestFactory(
|
261
315
|
client_id=client_id,
|
@@ -265,6 +319,16 @@ class AnaplanRefreshTokenAuth(_AnaplanAuth):
|
|
265
319
|
)
|
266
320
|
super().__init__(self._oauth_token["access_token"])
|
267
321
|
|
322
|
+
@property
|
323
|
+
def token(self) -> dict[str, str]:
|
324
|
+
"""
|
325
|
+
Returns the current token dictionary. You can safely use the `access_token`, but if you
|
326
|
+
must not use the `refresh_token` outside of this class, if you expect to use this instance
|
327
|
+
further. If you do use the `refresh_token` outside of this class, this will error on the
|
328
|
+
next attempt to refresh the token, as the `refresh_token` can only be used once.
|
329
|
+
"""
|
330
|
+
return self._oauth_token
|
331
|
+
|
268
332
|
def _build_auth_request(self) -> httpx.Request:
|
269
333
|
return self._oauth.refresh_token_request(self._oauth_token["refresh_token"])
|
270
334
|
|
anaplan_sdk/models/_bulk.py
CHANGED
@@ -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.
|
3
|
+
Version: 0.4.4a4
|
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
|
@@ -14,6 +14,8 @@ Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
14
14
|
Requires-Dist: pydantic<3.0.0,>=2.7.2
|
15
15
|
Provides-Extra: cert
|
16
16
|
Requires-Dist: cryptography<46.0.0,>=42.0.7; extra == 'cert'
|
17
|
+
Provides-Extra: keyring
|
18
|
+
Requires-Dist: keyring<26.0.0,>=25.6.0; extra == 'keyring'
|
17
19
|
Provides-Extra: oauth
|
18
20
|
Requires-Dist: oauthlib<4.0.0,>=3.0.0; extra == 'oauth'
|
19
21
|
Description-Content-Type: text/markdown
|
@@ -1,5 +1,5 @@
|
|
1
|
-
anaplan_sdk/__init__.py,sha256=
|
2
|
-
anaplan_sdk/_auth.py,sha256=
|
1
|
+
anaplan_sdk/__init__.py,sha256=WScEKtXlnRLjCb-j3qW9W4kEACTyPsTLFs-L54et2TQ,351
|
2
|
+
anaplan_sdk/_auth.py,sha256=H5A_fujTMHf7J33w2jkqMYtlxKFwzqZgxFcBJHi8wvc,16612
|
3
3
|
anaplan_sdk/_base.py,sha256=9CdLshORWsLixOyoFa3A0Bka5lhLwlZrQI5sEdBcGFI,12298
|
4
4
|
anaplan_sdk/_oauth.py,sha256=AynlJDrGIinQT0jwxI2RSvtU4D7Wasyw3H1uicdlLVI,12672
|
5
5
|
anaplan_sdk/exceptions.py,sha256=ALkA9fBF0NQ7dufFxV6AivjmHyuJk9DOQ9jtJV2n7f0,1809
|
@@ -20,11 +20,11 @@ anaplan_sdk/_clients/_transactional.py,sha256=YUVbA54uhMloQcahwMtmZO3YooO6qQzwZN
|
|
20
20
|
anaplan_sdk/models/__init__.py,sha256=nSplwPG_74CG9CKbv1PzP9bsA9v5-daS4azpTCvCQTI,925
|
21
21
|
anaplan_sdk/models/_alm.py,sha256=IqsTPvkx_ujLpaqZgIrTcr44KHJyKc4dyeRs9rkDjms,2307
|
22
22
|
anaplan_sdk/models/_base.py,sha256=6AZc9CfireUKgpZfMxYKu4MbwiyHQOsGLjKrxGXBLic,508
|
23
|
-
anaplan_sdk/models/_bulk.py,sha256=
|
23
|
+
anaplan_sdk/models/_bulk.py,sha256=_lHARGGjJgi-AmA7u5ZfCmGpLecPnr73LSAsZSX-a_A,8276
|
24
24
|
anaplan_sdk/models/_transactional.py,sha256=_0UbVR9D5QABI29yloYrJTSgL-K0EU7PzPeJu5LdhnY,4854
|
25
25
|
anaplan_sdk/models/cloud_works.py,sha256=nfn_LHPR-KmW7Tpvz-5qNCzmR8SYgvsVV-lx5iDlyqI,19425
|
26
26
|
anaplan_sdk/models/flows.py,sha256=SuLgNj5-2SeE3U1i8iY8cq2IkjuUgd_3M1n2ENructk,3625
|
27
|
-
anaplan_sdk-0.4.
|
28
|
-
anaplan_sdk-0.4.
|
29
|
-
anaplan_sdk-0.4.
|
30
|
-
anaplan_sdk-0.4.
|
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,,
|
File without changes
|
File without changes
|