anaplan-sdk 0.4.2__tar.gz → 0.4.3a2__tar.gz
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-0.4.2 → anaplan_sdk-0.4.3a2}/.gitignore +1 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/PKG-INFO +1 -1
- anaplan_sdk-0.4.3a2/anaplan_sdk/__init__.py +15 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_async_clients/_bulk.py +37 -77
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_auth.py +126 -125
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_clients/_bulk.py +39 -69
- anaplan_sdk-0.4.3a2/anaplan_sdk/_oauth.py +257 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/async/async_alm_client.md +6 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/async/async_audit_client.md +6 -0
- anaplan_sdk-0.4.3a2/docs/api/async/async_client.md +9 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/async/async_cw_client.md +6 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/async/async_flows_client.md +6 -0
- anaplan_sdk-0.4.3a2/docs/api/async/async_oauth_client.md +9 -0
- anaplan_sdk-0.4.3a2/docs/api/async/async_transactional_client.md +11 -0
- anaplan_sdk-0.4.3a2/docs/api/exceptions.md +9 -0
- anaplan_sdk-0.4.3a2/docs/api/models/alm.md +7 -0
- anaplan_sdk-0.4.3a2/docs/api/models/bulk.md +7 -0
- anaplan_sdk-0.4.3a2/docs/api/models/cloud_works.md +7 -0
- anaplan_sdk-0.4.3a2/docs/api/models/flows.md +7 -0
- anaplan_sdk-0.4.3a2/docs/api/models/transactional.md +7 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/sync/sync_alm_client.md +6 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/sync/sync_audit_client.md +6 -0
- anaplan_sdk-0.4.3a2/docs/api/sync/sync_client.md +9 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/sync/sync_cw_client.md +6 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/sync/sync_flows_client.md +6 -0
- anaplan_sdk-0.4.3a2/docs/api/sync/sync_oauth_client.md +10 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/api/sync/sync_transactional_client.md +6 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/css/styles.css +3 -3
- anaplan_sdk-0.4.3a2/docs/guides/authentication.md +283 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/index.md +1 -1
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/quickstart.md +3 -3
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/mkdocs.yml +10 -2
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/pyproject.toml +101 -102
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/uv.lock +1164 -1180
- anaplan_sdk-0.4.2/anaplan_sdk/__init__.py +0 -4
- anaplan_sdk-0.4.2/docs/api/async/async_client.md +0 -1
- anaplan_sdk-0.4.2/docs/api/async/async_transactional_client.md +0 -5
- anaplan_sdk-0.4.2/docs/api/exceptions.md +0 -4
- anaplan_sdk-0.4.2/docs/api/models.md +0 -4
- anaplan_sdk-0.4.2/docs/api/sync/sync_client.md +0 -1
- anaplan_sdk-0.4.2/docs/guides/authentication.md +0 -246
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/.github/dependabot.yml +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/.github/workflows/docs.yml +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/.github/workflows/lint.yml +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/.github/workflows/tests.yml +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/.pre-commit-config.yaml +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/LICENSE +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/README.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_async_clients/__init__.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_async_clients/_alm.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_async_clients/_audit.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_async_clients/_cloud_works.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_async_clients/_cw_flow.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_async_clients/_transactional.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_base.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_clients/__init__.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_clients/_alm.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_clients/_audit.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_clients/_cloud_works.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_clients/_cw_flow.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/_clients/_transactional.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/exceptions.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/models/__init__.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/models/_alm.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/models/_base.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/models/_bulk.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/models/_transactional.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/models/cloud_works.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/anaplan_sdk/models/flows.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/anaplan_explained.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/assets/overview.html +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/alm.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/audit.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/bulk.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/bulk_vs_transactional.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/cloud_works.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/logging.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/multiple_models.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/guides/transactional.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/img/anaplan-sdk.webp +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/index.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/installation.md +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/js/assets/hljs.js +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/js/assets/hljs.min.js +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/js/assets/python.js +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/js/assets/python.min.js +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/js/highlight.js +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/docs/js/highlight.min.js +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/async/conftest.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/async/test_async_alm_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/async/test_async_audit_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/async/test_async_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/async/test_async_cloud_works_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/async/test_async_flows_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/async/test_async_transactional_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/conftest.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/sync/conftest.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/sync/test_alm_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/sync/test_audit_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/sync/test_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/sync/test_cloud_works_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/sync/test_flows_client.py +0 -0
- {anaplan_sdk-0.4.2 → anaplan_sdk-0.4.3a2}/tests/sync/test_transactional_client.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: anaplan-sdk
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.3a2
|
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
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from ._async_clients import AsyncClient
|
2
|
+
from ._auth import AnaplanOAuthCodeAuth, AnaplanRefreshTokenAuth
|
3
|
+
from ._clients import Client
|
4
|
+
from ._oauth import AsyncOauth, Oauth
|
5
|
+
|
6
|
+
__all__ = [
|
7
|
+
"AsyncClient",
|
8
|
+
"Client",
|
9
|
+
"AnaplanOAuthCodeAuth",
|
10
|
+
"AnaplanRefreshTokenAuth",
|
11
|
+
"AsyncOauth",
|
12
|
+
"Oauth",
|
13
|
+
"models",
|
14
|
+
"exceptions",
|
15
|
+
]
|
@@ -6,7 +6,7 @@ from typing import AsyncIterator, Iterator
|
|
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 _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 (
|
@@ -32,14 +32,8 @@ logger = logging.getLogger("anaplan_sdk")
|
|
32
32
|
|
33
33
|
class AsyncClient(_AsyncBaseClient):
|
34
34
|
"""
|
35
|
-
|
36
|
-
|
37
|
-
abstractions over the API, so you can deal with python objects and simple functions rather than
|
38
|
-
implementation details like http, json, compression, chunking etc.
|
39
|
-
|
40
|
-
|
41
|
-
For more information, quick start guides and detailed instructions refer to:
|
42
|
-
[Anaplan SDK](https://vinzenzklass.github.io/anaplan-sdk).
|
35
|
+
Asynchronous Anaplan Client. For guides and examples
|
36
|
+
refer to https://vinzenzklass.github.io/anaplan-sdk.
|
43
37
|
"""
|
44
38
|
|
45
39
|
def __init__(
|
@@ -51,13 +45,8 @@ class AsyncClient(_AsyncBaseClient):
|
|
51
45
|
certificate: str | bytes | None = None,
|
52
46
|
private_key: str | bytes | None = None,
|
53
47
|
private_key_password: str | bytes | None = None,
|
54
|
-
|
55
|
-
|
56
|
-
redirect_uri: str | None = None,
|
57
|
-
refresh_token: str | None = None,
|
58
|
-
oauth2_scope: str = "openid profile email offline_access",
|
59
|
-
on_auth_code: AuthCodeCallback = None,
|
60
|
-
on_token_refresh: AuthTokenRefreshCallback = None,
|
48
|
+
token: str | None = None,
|
49
|
+
auth: httpx.Auth | None = None,
|
61
50
|
timeout: float | httpx.Timeout = 30,
|
62
51
|
retry_count: int = 2,
|
63
52
|
status_poll_delay: int = 1,
|
@@ -65,84 +54,55 @@ class AsyncClient(_AsyncBaseClient):
|
|
65
54
|
allow_file_creation: bool = False,
|
66
55
|
) -> None:
|
67
56
|
"""
|
68
|
-
|
69
|
-
https://
|
70
|
-
so you can deal with python objects and simple functions rather than implementation details
|
71
|
-
like http, json, compression, chunking etc.
|
72
|
-
|
73
|
-
|
74
|
-
For more information, quick start guides and detailed instructions refer to:
|
75
|
-
https://vinzenzklass.github.io/anaplan-sdk.
|
57
|
+
Asynchronous Anaplan Client. For guides and examples
|
58
|
+
refer to https://vinzenzklass.github.io/anaplan-sdk.
|
76
59
|
|
77
60
|
:param workspace_id: The Anaplan workspace Id. You can copy this from the browser URL or
|
78
|
-
|
61
|
+
find them using an HTTP Client like Postman, Paw, Insomnia etc.
|
79
62
|
:param model_id: The identifier of the model.
|
80
63
|
:param user_email: A valid email registered with the Anaplan Workspace you are attempting
|
81
|
-
|
82
|
-
:param password: Password for the given `user_email
|
83
|
-
|
84
|
-
|
85
|
-
:param
|
86
|
-
|
87
|
-
:param
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
:param
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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.
|
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.
|
107
|
-
With this you can for example securely store the token in your
|
108
|
-
application or on your server for later reuse. The function
|
109
|
-
must accept a single argument, which is the token dictionary
|
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.
|
64
|
+
to access.
|
65
|
+
:param password: Password for the given `user_email` for basic Authentication.
|
66
|
+
:param certificate: The certificate content or the absolute path to the certificate file.
|
67
|
+
:param private_key: The private key content or the absolute path to the private key file.
|
68
|
+
:param private_key_password: The password to access the private key file. This is only
|
69
|
+
considered if you provided a private key file and it password-protected.
|
70
|
+
:param token: An Anaplan API Token. This will be used to authenticate the client. If
|
71
|
+
sufficient other authentication parameters are provided, the token will be used
|
72
|
+
until it expires, after which a new one will be created. If you provide only this
|
73
|
+
parameter, the client will raise an error upon first authentication failure. For
|
74
|
+
short-lived instances, such as in web applications where user specific clients are
|
75
|
+
created, this is the recommended way to authenticate, since this has the least
|
76
|
+
overhead.
|
77
|
+
:param auth: You can provide a subclass of `httpx.Auth` to use for authentication. You can
|
78
|
+
provide an instance of one of the classes provided by the SDK, or an instance of
|
79
|
+
your own subclass of `httpx.Auth`. This will give you full control over the
|
80
|
+
authentication process, but you will need to implement the entire authentication
|
81
|
+
logic yourself.
|
116
82
|
:param timeout: The timeout in seconds for the HTTP requests. Alternatively, you can pass
|
117
|
-
|
83
|
+
an instance of `httpx.Timeout` to set the timeout for the HTTP requests.
|
118
84
|
:param retry_count: The number of times to retry an HTTP request if it fails. Set this to 0
|
119
|
-
|
120
|
-
|
85
|
+
to never retry. Defaults to 2, meaning each HTTP Operation will be tried a total
|
86
|
+
number of 2 times.
|
121
87
|
:param status_poll_delay: The delay between polling the status of a task.
|
122
88
|
:param upload_chunk_size: The size of the chunks to upload. This is the maximum size of
|
123
|
-
|
89
|
+
each chunk. Defaults to 25MB.
|
124
90
|
:param allow_file_creation: Whether to allow the creation of new files. Defaults to False
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
creating new files and uploading content to them.
|
91
|
+
since this is typically unintentional and may well be unwanted behaviour in the API
|
92
|
+
altogether. A file that is created this way will not be referenced by any action in
|
93
|
+
anaplan until manually assigned so there is typically no value in dynamically
|
94
|
+
creating new files and uploading content to them.
|
130
95
|
"""
|
131
96
|
_client = httpx.AsyncClient(
|
132
97
|
auth=(
|
133
|
-
|
98
|
+
auth
|
99
|
+
or _create_auth(
|
100
|
+
token=token,
|
134
101
|
user_email=user_email,
|
135
102
|
password=password,
|
136
103
|
certificate=certificate,
|
137
104
|
private_key=private_key,
|
138
105
|
private_key_password=private_key_password,
|
139
|
-
client_id=client_id,
|
140
|
-
client_secret=client_secret,
|
141
|
-
redirect_uri=redirect_uri,
|
142
|
-
refresh_token=refresh_token,
|
143
|
-
oauth2_scope=oauth2_scope,
|
144
|
-
on_auth_code=on_auth_code,
|
145
|
-
on_token_refresh=on_token_refresh,
|
146
106
|
)
|
147
107
|
),
|
148
108
|
timeout=timeout,
|
@@ -1,14 +1,11 @@
|
|
1
|
-
import asyncio
|
2
|
-
import inspect
|
3
1
|
import logging
|
4
2
|
import os
|
5
|
-
import threading
|
6
3
|
from base64 import b64encode
|
7
|
-
from
|
8
|
-
from typing import Any, Awaitable, Callable, Coroutine
|
4
|
+
from typing import Awaitable, Callable
|
9
5
|
|
10
6
|
import httpx
|
11
7
|
|
8
|
+
from ._oauth import _OAuthRequestFactory
|
12
9
|
from .exceptions import AnaplanException, InvalidCredentialsException, InvalidPrivateKeyException
|
13
10
|
|
14
11
|
logger = logging.getLogger("anaplan_sdk")
|
@@ -22,12 +19,12 @@ AuthTokenRefreshCallback = (
|
|
22
19
|
class _AnaplanAuth(httpx.Auth):
|
23
20
|
requires_response_body = True
|
24
21
|
|
25
|
-
def __init__(self,
|
26
|
-
|
22
|
+
def __init__(self, token: str | None = None):
|
23
|
+
self._token: str = token or ""
|
24
|
+
if not token:
|
27
25
|
logger.info("Creating Authentication Token.")
|
28
26
|
with httpx.Client(timeout=15.0) as client:
|
29
|
-
|
30
|
-
self._parse_auth_response(res)
|
27
|
+
self._parse_auth_response(client.send(self._build_auth_request()))
|
31
28
|
|
32
29
|
def _build_auth_request(self) -> httpx.Request:
|
33
30
|
raise NotImplementedError("Must be implemented in subclass.")
|
@@ -47,14 +44,25 @@ class _AnaplanAuth(httpx.Auth):
|
|
47
44
|
raise InvalidCredentialsException
|
48
45
|
if not response.is_success:
|
49
46
|
raise AnaplanException(f"Authentication failed: {response.status_code} {response.text}")
|
50
|
-
self._token
|
47
|
+
self._token = response.json()["tokenInfo"]["tokenValue"]
|
51
48
|
|
52
49
|
|
53
|
-
class
|
54
|
-
def __init__(self,
|
50
|
+
class _StaticTokenAuth(httpx.Auth):
|
51
|
+
def __init__(self, token: str):
|
52
|
+
self._token = token
|
53
|
+
|
54
|
+
def auth_flow(self, request):
|
55
|
+
request.headers["Authorization"] = f"AnaplanAuthToken {self._token}"
|
56
|
+
response = yield request
|
57
|
+
if response.status_code == 401:
|
58
|
+
raise InvalidCredentialsException("Token is invalid or expired.")
|
59
|
+
|
60
|
+
|
61
|
+
class _AnaplanBasicAuth(_AnaplanAuth):
|
62
|
+
def __init__(self, user_email: str, password: str, token: str | None = None):
|
55
63
|
self.user_email = user_email
|
56
64
|
self.password = password
|
57
|
-
super().__init__()
|
65
|
+
super().__init__(token)
|
58
66
|
|
59
67
|
def _build_auth_request(self) -> httpx.Request:
|
60
68
|
cred = b64encode(f"{self.user_email}:{self.password}".encode()).decode()
|
@@ -65,7 +73,7 @@ class AnaplanBasicAuth(_AnaplanAuth):
|
|
65
73
|
)
|
66
74
|
|
67
75
|
|
68
|
-
class
|
76
|
+
class _AnaplanCertAuth(_AnaplanAuth):
|
69
77
|
requires_request_body = True
|
70
78
|
|
71
79
|
def __init__(
|
@@ -73,10 +81,11 @@ class AnaplanCertAuth(_AnaplanAuth):
|
|
73
81
|
certificate: str | bytes,
|
74
82
|
private_key: str | bytes,
|
75
83
|
private_key_password: str | bytes | None = None,
|
84
|
+
token: str | None = None,
|
76
85
|
):
|
77
86
|
self.__set_certificate(certificate)
|
78
87
|
self.__set_private_key(private_key, private_key_password)
|
79
|
-
super().__init__()
|
88
|
+
super().__init__(token)
|
80
89
|
|
81
90
|
def _build_auth_request(self) -> httpx.Request:
|
82
91
|
encoded_cert, encoded_string, encoded_signed_string = self._prep_credentials()
|
@@ -145,150 +154,142 @@ class AnaplanCertAuth(_AnaplanAuth):
|
|
145
154
|
raise InvalidPrivateKeyException from error
|
146
155
|
|
147
156
|
|
148
|
-
class
|
157
|
+
class AnaplanOAuthCodeAuth(_AnaplanAuth):
|
149
158
|
def __init__(
|
150
159
|
self,
|
151
160
|
client_id: str,
|
152
161
|
client_secret: str,
|
153
|
-
|
154
|
-
|
162
|
+
redirect_url: str,
|
163
|
+
token: dict[str, str] | None = None,
|
164
|
+
authorization_url: str = "https://us1a.app.anaplan.com/auth/prelogin",
|
165
|
+
token_url: str = "https://us1a.app.anaplan.com/oauth/token",
|
166
|
+
validation_url: str = "https://auth.anaplan.com/token/validate",
|
155
167
|
scope: str = "openid profile email offline_access",
|
156
|
-
|
157
|
-
on_token_refresh: AuthTokenRefreshCallback = None,
|
168
|
+
state_generator: Callable[[], str] | None = None,
|
158
169
|
):
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
170
|
+
"""
|
171
|
+
Initializes the AnaplanOAuthCodeAuth class for OAuth2 authentication using the
|
172
|
+
Authorization Code Flow. This is a utility class for local development and requires user
|
173
|
+
interaction. For Web Applications and other scenarios, refer to `Oauth` or `AsyncOauth`.
|
174
|
+
This class will refresh the access token automatically when it expires.
|
175
|
+
:param client_id: The client ID of your Anaplan Oauth 2.0 application. This Application
|
176
|
+
must be an Authorization Code Grant application.
|
177
|
+
:param client_secret: The client secret of your Anaplan Oauth 2.0 application.
|
178
|
+
:param redirect_url: The URL to which the user will be redirected after authorizing the
|
179
|
+
application.
|
180
|
+
:param authorization_url: The URL to which the user will be redirected to authorize the
|
181
|
+
application. Defaults to the Anaplan Prelogin Page, where the user can select the
|
182
|
+
login method.
|
183
|
+
:param token_url: The URL to post the authorization code to in order to fetch the access
|
184
|
+
token.
|
185
|
+
:param validation_url: The URL to validate the access token.
|
186
|
+
:param scope: The scope of the access request.
|
187
|
+
:param state_generator: A callable that generates a random state string. You can optionally
|
188
|
+
pass this if you need to customize the state generation logic. If not provided,
|
189
|
+
the state will be generated by `oauthlib`.
|
190
|
+
"""
|
191
|
+
|
192
|
+
self._oauth_token = token or {}
|
193
|
+
self._oauth = _OAuthRequestFactory(
|
194
|
+
client_id=client_id,
|
195
|
+
client_secret=client_secret,
|
196
|
+
redirect_url=redirect_url,
|
197
|
+
scope=scope,
|
198
|
+
authorization_url=authorization_url,
|
199
|
+
token_url=token_url,
|
200
|
+
validation_url=validation_url,
|
201
|
+
state_generator=state_generator,
|
168
202
|
)
|
169
|
-
|
170
|
-
self._client_id = client_id
|
171
|
-
self._client_secret = client_secret
|
172
|
-
self._redirect_uri = redirect_uri
|
173
|
-
self._refresh_token = refresh_token
|
174
|
-
self._scope = scope
|
175
|
-
self._id_token = None
|
176
|
-
self._on_auth_code = on_auth_code
|
177
|
-
self._on_token_refresh = on_token_refresh
|
178
|
-
if not refresh_token:
|
203
|
+
if not token:
|
179
204
|
self.__auth_code_flow()
|
180
|
-
super().__init__(
|
205
|
+
super().__init__(self._token)
|
181
206
|
|
182
207
|
def _build_auth_request(self) -> httpx.Request:
|
183
|
-
|
184
|
-
token_url=self._token_url,
|
185
|
-
refresh_token=self._refresh_token,
|
186
|
-
client_secret=self._client_secret,
|
187
|
-
client_id=self._client_id,
|
188
|
-
)
|
189
|
-
return httpx.Request(method="post", url=url, headers=headers, content=body)
|
208
|
+
return self._oauth.refresh_token_request(self._oauth_token["refresh_token"])
|
190
209
|
|
191
210
|
def _parse_auth_response(self, response: httpx.Response) -> None:
|
192
211
|
if response.status_code == 401:
|
193
212
|
raise InvalidCredentialsException
|
194
213
|
if not response.is_success:
|
195
214
|
raise AnaplanException(f"Authentication failed: {response.status_code} {response.text}")
|
196
|
-
|
197
|
-
self._token =
|
198
|
-
self._refresh_token = token["refresh_token"]
|
199
|
-
if self._on_token_refresh:
|
200
|
-
_run_callback(self._on_token_refresh, token)
|
201
|
-
self._id_token = token.get("id_token")
|
215
|
+
self._oauth_token = response.json()
|
216
|
+
self._token: str = self._oauth_token["access_token"]
|
202
217
|
|
203
218
|
def __auth_code_flow(self):
|
204
219
|
from oauthlib.oauth2 import OAuth2Error
|
205
220
|
|
206
221
|
try:
|
207
222
|
logger.info("Creating Authentication Token with OAuth2 Authorization Code Flow.")
|
208
|
-
url, _
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
)
|
213
|
-
authorization_response = (
|
214
|
-
_run_callback(self._on_auth_code, url)
|
215
|
-
if self._on_auth_code
|
216
|
-
else input(
|
217
|
-
f"Please go to {url} and authorize the app.\n"
|
218
|
-
"Then paste the entire redirect URL here: "
|
219
|
-
)
|
220
|
-
)
|
221
|
-
url, headers, body = self._oauth.prepare_token_request(
|
222
|
-
token_url=self._token_url,
|
223
|
-
redirect_url=self._redirect_uri,
|
224
|
-
authorization_response=authorization_response,
|
225
|
-
client_secret=self._client_secret,
|
223
|
+
url, _ = self._oauth.authorization_url()
|
224
|
+
authorization_response = input(
|
225
|
+
f"Please go to {url} and authorize the app.\n"
|
226
|
+
"Then paste the entire redirect URL here: "
|
226
227
|
)
|
227
|
-
|
228
|
+
with httpx.Client() as client:
|
229
|
+
res = client.send(self._oauth.token_request(authorization_response))
|
230
|
+
self._parse_auth_response(res)
|
228
231
|
except (httpx.HTTPError, ValueError, TypeError, OAuth2Error) as error:
|
229
232
|
raise InvalidCredentialsException("Error during OAuth2 authorization flow.") from error
|
230
233
|
|
231
234
|
|
232
|
-
|
235
|
+
class AnaplanRefreshTokenAuth(_AnaplanAuth):
|
236
|
+
def __init__(
|
237
|
+
self,
|
238
|
+
client_id: str,
|
239
|
+
client_secret: str,
|
240
|
+
redirect_url: str,
|
241
|
+
token: dict[str, str],
|
242
|
+
token_url: str = "https://us1a.app.anaplan.com/oauth/token",
|
243
|
+
):
|
244
|
+
"""
|
245
|
+
This class is a utility class for long-lived `Client` or `AsyncClient` instances that use
|
246
|
+
OAuth. It expects that you have a valid OAuth token with a refresh token, which will be used
|
247
|
+
to refresh the access token when it expires.
|
248
|
+
:param client_id: The client ID of your Anaplan Oauth 2.0 application. This Application
|
249
|
+
must be an Authorization Code Grant application.
|
250
|
+
:param client_secret: The client secret of your Anaplan Oauth 2.0 application.
|
251
|
+
:param redirect_url: The URL to which the user will be redirected after authorizing the
|
252
|
+
application.
|
253
|
+
:param token_url: The URL to post the refresh token request to in order to fetch the access
|
254
|
+
token.
|
255
|
+
"""
|
256
|
+
self._oauth_token = token
|
257
|
+
self._oauth = _OAuthRequestFactory(
|
258
|
+
client_id=client_id,
|
259
|
+
client_secret=client_secret,
|
260
|
+
redirect_url=redirect_url,
|
261
|
+
token_url=token_url,
|
262
|
+
)
|
263
|
+
super().__init__(self._token)
|
264
|
+
|
265
|
+
def _build_auth_request(self) -> httpx.Request:
|
266
|
+
return self._oauth.refresh_token_request(self._oauth_token["refresh_token"])
|
267
|
+
|
268
|
+
def _parse_auth_response(self, response: httpx.Response) -> None:
|
269
|
+
if response.status_code == 401:
|
270
|
+
raise InvalidCredentialsException
|
271
|
+
if not response.is_success:
|
272
|
+
raise AnaplanException(f"Authentication failed: {response.status_code} {response.text}")
|
273
|
+
self._oauth_token = response.json()
|
274
|
+
self._token: str = self._oauth_token["access_token"]
|
275
|
+
|
276
|
+
|
277
|
+
def _create_auth(
|
233
278
|
user_email: str | None = None,
|
234
279
|
password: str | None = None,
|
235
280
|
certificate: str | bytes | None = None,
|
236
281
|
private_key: str | bytes | None = None,
|
237
282
|
private_key_password: str | bytes | None = None,
|
238
|
-
|
239
|
-
|
240
|
-
redirect_uri: str | None = None,
|
241
|
-
refresh_token: str | None = None,
|
242
|
-
oauth2_scope: str = "openid profile email offline_access",
|
243
|
-
on_auth_code: AuthCodeCallback = None,
|
244
|
-
on_token_refresh: AuthTokenRefreshCallback = None,
|
245
|
-
) -> _AnaplanAuth:
|
283
|
+
token: str | None = None,
|
284
|
+
) -> httpx.Auth:
|
246
285
|
if certificate and private_key:
|
247
|
-
return
|
286
|
+
return _AnaplanCertAuth(certificate, private_key, private_key_password, token)
|
248
287
|
if user_email and password:
|
249
|
-
return
|
250
|
-
if
|
251
|
-
return
|
252
|
-
client_id=client_id,
|
253
|
-
client_secret=client_secret,
|
254
|
-
redirect_uri=redirect_uri,
|
255
|
-
refresh_token=refresh_token,
|
256
|
-
scope=oauth2_scope,
|
257
|
-
on_auth_code=on_auth_code,
|
258
|
-
on_token_refresh=on_token_refresh,
|
259
|
-
)
|
288
|
+
return _AnaplanBasicAuth(user_email, password, token)
|
289
|
+
if token:
|
290
|
+
return _StaticTokenAuth(token)
|
260
291
|
raise ValueError(
|
261
292
|
"No valid authentication parameters provided. Please provide either:\n"
|
262
|
-
"- user_email and password
|
263
|
-
"- certificate and private_key
|
264
|
-
"- client_id, client_secret, and redirect_uri"
|
293
|
+
"- `user_email` and `password`, or\n"
|
294
|
+
"- `certificate` and `private_key`\n"
|
265
295
|
)
|
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()
|