aplos-nca-saas-sdk 0.0.11__py3-none-any.whl → 0.0.13__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.
- aplos_nca_saas_sdk/integration_testing/configs/_config_base.py +7 -2
- aplos_nca_saas_sdk/integration_testing/configs/app_settings_config.py +88 -0
- aplos_nca_saas_sdk/integration_testing/configs/config_sample.json +31 -18
- aplos_nca_saas_sdk/integration_testing/configs/file_upload_config.py +94 -0
- aplos_nca_saas_sdk/integration_testing/configs/{login.py → login_config.py} +36 -23
- aplos_nca_saas_sdk/integration_testing/configs/nca_execution_config.py +186 -0
- aplos_nca_saas_sdk/integration_testing/files/executions/config1.json +46 -0
- aplos_nca_saas_sdk/integration_testing/integration_test_base.py +10 -3
- aplos_nca_saas_sdk/integration_testing/integration_test_configurations.py +15 -7
- aplos_nca_saas_sdk/integration_testing/integration_test_factory.py +1 -1
- aplos_nca_saas_sdk/integration_testing/integration_test_response.py +1 -1
- aplos_nca_saas_sdk/integration_testing/integration_test_suite.py +10 -3
- aplos_nca_saas_sdk/integration_testing/main.py +6 -6
- aplos_nca_saas_sdk/integration_testing/readme.md +1 -1
- aplos_nca_saas_sdk/integration_testing/tests/app_configuration_test.py +3 -3
- aplos_nca_saas_sdk/integration_testing/tests/app_login_test.py +4 -4
- aplos_nca_saas_sdk/integration_testing/tests/file_upload_test.py +44 -92
- aplos_nca_saas_sdk/integration_testing/tests/nca_analysis_test.py +89 -0
- aplos_nca_saas_sdk/integration_testing/tests/{app_execution_test.py → validation_test.py} +1 -1
- aplos_nca_saas_sdk/nca_resources/_api_base.py +44 -0
- aplos_nca_saas_sdk/{aws_resources → nca_resources}/aws_cognito.py +13 -7
- aplos_nca_saas_sdk/{aws_resources → nca_resources}/aws_s3_presigned_payload.py +3 -3
- aplos_nca_saas_sdk/{aws_resources → nca_resources}/aws_s3_presigned_upload.py +28 -22
- aplos_nca_saas_sdk/nca_resources/nca_analysis.py +304 -0
- aplos_nca_saas_sdk/nca_resources/nca_app_configuration.py +4 -4
- aplos_nca_saas_sdk/nca_resources/{nca_login.py → nca_authenticator.py} +26 -23
- aplos_nca_saas_sdk/nca_resources/nca_endpoints.py +56 -30
- aplos_nca_saas_sdk/nca_resources/nca_file_download.py +130 -0
- aplos_nca_saas_sdk/nca_resources/nca_file_upload.py +27 -29
- aplos_nca_saas_sdk/nca_resources/nca_validations.py +34 -0
- aplos_nca_saas_sdk/run_analysis_execution.py +148 -0
- aplos_nca_saas_sdk/utilities/commandline_args.py +32 -38
- aplos_nca_saas_sdk/utilities/environment_services.py +3 -2
- aplos_nca_saas_sdk/utilities/environment_vars.py +17 -4
- aplos_nca_saas_sdk/utilities/file_utility.py +33 -0
- aplos_nca_saas_sdk/utilities/http_utility.py +5 -14
- aplos_nca_saas_sdk/version.py +1 -1
- {aplos_nca_saas_sdk-0.0.11.dist-info → aplos_nca_saas_sdk-0.0.13.dist-info}/METADATA +1 -1
- aplos_nca_saas_sdk-0.0.13.dist-info/RECORD +51 -0
- {aplos_nca_saas_sdk-0.0.11.dist-info → aplos_nca_saas_sdk-0.0.13.dist-info}/licenses/LICENSE +1 -1
- aplos_nca_saas_sdk/integration_testing/configs/app_settings.py +0 -88
- aplos_nca_saas_sdk/integration_testing/configs/file_upload.py +0 -104
- aplos_nca_saas_sdk/integration_testing/tests/app_validation_test.py +0 -5
- aplos_nca_saas_sdk/nca_resources/nca_executions.py +0 -387
- aplos_nca_saas_sdk-0.0.11.dist-info/RECORD +0 -44
- /aplos_nca_saas_sdk/{files/analysis_files/single_ev/configuration_single_ev.json → sample_files/analysis_files/single_ev/config.json} +0 -0
- /aplos_nca_saas_sdk/{files/analysis_files/single_ev/single_ev.csv → sample_files/analysis_files/single_ev/input.csv} +0 -0
- /aplos_nca_saas_sdk/{files → sample_files}/analysis_files/single_ev/meta_data.json +0 -0
- {aplos_nca_saas_sdk-0.0.11.dist-info → aplos_nca_saas_sdk-0.0.13.dist-info}/WHEEL +0 -0
@@ -1,5 +1,5 @@
|
|
1
1
|
"""
|
2
|
-
Copyright 2024 Aplos Analytics
|
2
|
+
Copyright 2024-2025 Aplos Analytics
|
3
3
|
All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
|
4
4
|
Property of Aplos Analytics, Utah, USA
|
5
5
|
"""
|
@@ -9,20 +9,23 @@ import os
|
|
9
9
|
from typing import Any, Dict
|
10
10
|
|
11
11
|
import requests
|
12
|
-
from aplos_nca_saas_sdk.
|
13
|
-
|
12
|
+
from aplos_nca_saas_sdk.nca_resources.aws_s3_presigned_payload import (
|
13
|
+
S3PresignedUrlPayload,
|
14
14
|
)
|
15
|
-
from aplos_nca_saas_sdk.utilities.http_utility import HttpUtilities
|
15
|
+
from aplos_nca_saas_sdk.utilities.http_utility import HttpUtilities
|
16
|
+
from aplos_nca_saas_sdk.nca_resources._api_base import NCAApiBaseClass
|
16
17
|
|
17
18
|
|
18
|
-
class
|
19
|
-
"""
|
19
|
+
class S3PresignedUrlUpload(NCAApiBaseClass):
|
20
|
+
"""S3PresignedUrlUpload"""
|
20
21
|
|
21
|
-
def __init__(self,
|
22
|
-
|
23
|
-
self.jwt = jwt
|
22
|
+
def __init__(self, host: str) -> None:
|
23
|
+
super().__init__(host)
|
24
24
|
|
25
|
-
def upload_file(
|
25
|
+
def upload_file(
|
26
|
+
self,
|
27
|
+
input_file: str,
|
28
|
+
) -> Dict[str, Any]:
|
26
29
|
"""
|
27
30
|
Uploads a file to your Aplos Cloud Account in AWS
|
28
31
|
|
@@ -34,15 +37,17 @@ class S3PresignedUpload:
|
|
34
37
|
"""
|
35
38
|
|
36
39
|
# get the presigned url for uploading
|
37
|
-
paylod:
|
38
|
-
input_file=input_file
|
40
|
+
paylod: S3PresignedUrlPayload = self.__get_presigned_upload_info(
|
41
|
+
input_file=input_file, jwt=self.authenticator.cognito.jwt
|
39
42
|
)
|
40
43
|
# upload the files
|
41
44
|
upload_response = self.__upload_file_to_s3(paylod, input_file=input_file)
|
42
45
|
|
43
46
|
return upload_response
|
44
47
|
|
45
|
-
def __get_presigned_upload_info(
|
48
|
+
def __get_presigned_upload_info(
|
49
|
+
self, input_file: str, jwt: str
|
50
|
+
) -> S3PresignedUrlPayload:
|
46
51
|
"""
|
47
52
|
Performs all the necessary steps for creating a presigned url to upload a file to S3.
|
48
53
|
We're using AWS S3 presigned urls for security as well as allowing for very large files if required.
|
@@ -50,11 +55,11 @@ class S3PresignedUpload:
|
|
50
55
|
input_file (str): the path to the input (analysis) file
|
51
56
|
|
52
57
|
Returns:
|
53
|
-
|
58
|
+
S3PresignedUrlPayload: instance of S3PresignedUrlPayload
|
54
59
|
"""
|
55
60
|
|
56
|
-
url =
|
57
|
-
headers = HttpUtilities.get_headers(
|
61
|
+
url = self.endpoints.files
|
62
|
+
headers = HttpUtilities.get_headers(jwt)
|
58
63
|
|
59
64
|
body = {"file_name": input_file, "method_type": "post"}
|
60
65
|
response = requests.post(
|
@@ -64,29 +69,29 @@ class S3PresignedUpload:
|
|
64
69
|
if response.status_code == 403:
|
65
70
|
raise PermissionError(
|
66
71
|
"Failed to get a presigned url. "
|
67
|
-
f"Status Code: {response.status_code
|
72
|
+
f"Status Code: {response.status_code}"
|
68
73
|
f"Reason: {response.reason} "
|
69
74
|
f"403 Errors can also occur if you have an invalid path."
|
70
75
|
)
|
71
76
|
elif response.status_code != 200:
|
72
77
|
raise RuntimeError(
|
73
78
|
"Failed to get a presigned url. "
|
74
|
-
f"Status Code: {response.status_code
|
79
|
+
f"Status Code: {response.status_code}"
|
75
80
|
f"Reason: {response.reason}"
|
76
81
|
)
|
77
82
|
result = response.json()
|
78
83
|
|
79
|
-
payload:
|
84
|
+
payload: S3PresignedUrlPayload = S3PresignedUrlPayload(result)
|
80
85
|
|
81
86
|
return payload
|
82
87
|
|
83
88
|
def __upload_file_to_s3(
|
84
|
-
self, payload:
|
89
|
+
self, payload: S3PresignedUrlPayload, input_file: str
|
85
90
|
) -> Dict[str, Any]:
|
86
91
|
"""
|
87
92
|
Peforms the actual uploading via a presigned url for S3 bucket storage
|
88
93
|
Args:
|
89
|
-
payload (
|
94
|
+
payload (S3PresignedUrlPayload): instance of S3PresignedUrlPayload with all the data needed
|
90
95
|
input_file (str): the path to a file being uploaded
|
91
96
|
|
92
97
|
Raises:
|
@@ -104,8 +109,9 @@ class S3PresignedUpload:
|
|
104
109
|
with open(input_file, "rb") as file:
|
105
110
|
files = {"file": (input_file, file)}
|
106
111
|
# upload to s3 with the presigned url
|
112
|
+
# authentication is built into the url
|
107
113
|
upload_response = requests.post(
|
108
|
-
str(payload.url), data=payload.form_data, files=files, timeout=
|
114
|
+
str(payload.url), data=payload.form_data, files=files, timeout=60
|
109
115
|
)
|
110
116
|
|
111
117
|
# Check the response: 204 is a success in this case
|
@@ -0,0 +1,304 @@
|
|
1
|
+
"""
|
2
|
+
Copyright 2024-2025 Aplos Analytics
|
3
|
+
All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
|
4
|
+
Property of Aplos Analytics, Utah, USA
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
import os
|
9
|
+
import time
|
10
|
+
import zipfile
|
11
|
+
from datetime import datetime, timedelta
|
12
|
+
from pathlib import Path
|
13
|
+
from typing import Any, Dict
|
14
|
+
|
15
|
+
import requests
|
16
|
+
from aws_lambda_powertools import Logger
|
17
|
+
|
18
|
+
from aplos_nca_saas_sdk.nca_resources._api_base import NCAApiBaseClass
|
19
|
+
from aplos_nca_saas_sdk.nca_resources.aws_s3_presigned_upload import (
|
20
|
+
S3PresignedUrlUpload,
|
21
|
+
)
|
22
|
+
from aplos_nca_saas_sdk.utilities.environment_vars import EnvironmentVars
|
23
|
+
from aplos_nca_saas_sdk.utilities.http_utility import HttpUtilities
|
24
|
+
|
25
|
+
logger = Logger()
|
26
|
+
|
27
|
+
|
28
|
+
class NCAAnalysis(NCAApiBaseClass):
|
29
|
+
"""NCA Analysis API"""
|
30
|
+
|
31
|
+
def __init__(self, host: str) -> None:
|
32
|
+
super().__init__(host)
|
33
|
+
|
34
|
+
self.verbose: bool = False
|
35
|
+
|
36
|
+
def execute(
|
37
|
+
self,
|
38
|
+
username: str,
|
39
|
+
password: str,
|
40
|
+
input_file_path: str,
|
41
|
+
config_data: dict,
|
42
|
+
*,
|
43
|
+
meta_data: str | dict | None = None,
|
44
|
+
wait_for_results: bool = True,
|
45
|
+
max_wait_in_seconds: int = 900,
|
46
|
+
output_directory: str | None = None,
|
47
|
+
unzip_after_download: bool = False,
|
48
|
+
) -> Dict[str, Any]:
|
49
|
+
"""
|
50
|
+
Executes an analsysis.
|
51
|
+
- Uploads an analysis file.
|
52
|
+
- Adds the execution to the queue.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
username (str): the username
|
56
|
+
password (str): the users password
|
57
|
+
input_file_path (str): the path to the input (analysis) file
|
58
|
+
config_data (dict): analysis configuration information
|
59
|
+
meta_data (str | dict | None, optional): meta data attached to the execution. Defaults to None.
|
60
|
+
wait_for_results (bool, optional): should the program wait for results. Defaults to True.
|
61
|
+
max_wait_in_seconds (int optional): the max time to wait for a download
|
62
|
+
output_directory (str, optional): the output directory. Defaults to None (the local directory is used)
|
63
|
+
unzip_after_download (bool): Results are downloaded as a zip file, this option will unzip them automatically. Defaults to False
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
Dict[str, Any]: The execution response. If you wait for the completion
|
67
|
+
"""
|
68
|
+
|
69
|
+
self.log(f"\tLogging into {self.host}.")
|
70
|
+
|
71
|
+
self.authenticator.authenticate(username=username, password=password)
|
72
|
+
|
73
|
+
self.log("\tUploading the analysis file.")
|
74
|
+
uploader: S3PresignedUrlUpload = S3PresignedUrlUpload(str(self.host))
|
75
|
+
uploader.authenticator = self.authenticator
|
76
|
+
upload_response: Dict[str, Any] = uploader.upload_file(input_file_path)
|
77
|
+
|
78
|
+
file_id: str = upload_response.get("file_id", "")
|
79
|
+
if not file_id:
|
80
|
+
raise RuntimeError(
|
81
|
+
"Unexpected empty file_id when attempting to upload file."
|
82
|
+
)
|
83
|
+
|
84
|
+
self.log("\tAdding analyis to the queue.")
|
85
|
+
execution_response: Dict[str, Any] = self.__add_to_queue(
|
86
|
+
file_id=file_id,
|
87
|
+
config_data=config_data,
|
88
|
+
meta_data=meta_data,
|
89
|
+
)
|
90
|
+
|
91
|
+
execution_id: str = execution_response.get("execution_id", "")
|
92
|
+
|
93
|
+
response = {
|
94
|
+
"execution": execution_response,
|
95
|
+
"upload": upload_response,
|
96
|
+
"results": {"file": None},
|
97
|
+
}
|
98
|
+
|
99
|
+
if not execution_id:
|
100
|
+
raise RuntimeError(
|
101
|
+
"Unexpected empty execution_id when attempting to execute analysis."
|
102
|
+
)
|
103
|
+
|
104
|
+
if wait_for_results:
|
105
|
+
# wait for it
|
106
|
+
download_url = self.wait_for_results(
|
107
|
+
execution_id=execution_id, max_wait_in_seconds=max_wait_in_seconds
|
108
|
+
)
|
109
|
+
# download the files
|
110
|
+
if download_url is None:
|
111
|
+
raise RuntimeError(
|
112
|
+
"Unexpected empty download_url when attempting to download results."
|
113
|
+
)
|
114
|
+
else:
|
115
|
+
self.log("\tDownloading the results.")
|
116
|
+
file_path = self.download_file(
|
117
|
+
download_url,
|
118
|
+
output_directory=output_directory,
|
119
|
+
do_unzip=unzip_after_download,
|
120
|
+
)
|
121
|
+
|
122
|
+
response["results"]["file"] = file_path
|
123
|
+
else:
|
124
|
+
self.log("Bypassed results download.")
|
125
|
+
|
126
|
+
return response
|
127
|
+
|
128
|
+
def __add_to_queue(
|
129
|
+
self,
|
130
|
+
file_id: str,
|
131
|
+
config_data: dict,
|
132
|
+
meta_data: str | dict | None = None,
|
133
|
+
) -> Dict[str, Any]:
|
134
|
+
"""
|
135
|
+
Adds the analysis to the execution queue.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
bucket_name (str): s3 bucket name for your organization. this is returned to you
|
139
|
+
object_key (str): 3s object key for the file you are running an analysis on.
|
140
|
+
config_data (dict): the config_data for the analysis file
|
141
|
+
meta_data (str | dict): Optional. Any meta data you'd like attached to this execution
|
142
|
+
Returns:
|
143
|
+
Dict[str, Any]: the api response
|
144
|
+
"""
|
145
|
+
|
146
|
+
if not file_id:
|
147
|
+
raise ValueError("Missing file_id. Please provide a valid file_id.")
|
148
|
+
|
149
|
+
if not config_data:
|
150
|
+
raise ValueError(
|
151
|
+
"Missing config_data. Please provide a valid config_data."
|
152
|
+
)
|
153
|
+
headers = HttpUtilities.get_headers(self.authenticator.cognito.jwt)
|
154
|
+
# to start a new execution we need the location of the file (s3 bucket and object key)
|
155
|
+
# you basic configuration
|
156
|
+
# optional meta data
|
157
|
+
|
158
|
+
submission = {
|
159
|
+
"file": {"id": file_id},
|
160
|
+
"configuration": config_data,
|
161
|
+
"meta_data": meta_data,
|
162
|
+
}
|
163
|
+
|
164
|
+
response: requests.Response = requests.post(
|
165
|
+
self.endpoints.executions,
|
166
|
+
headers=headers,
|
167
|
+
data=json.dumps(submission),
|
168
|
+
timeout=30,
|
169
|
+
)
|
170
|
+
json_response: dict = response.json()
|
171
|
+
|
172
|
+
if response.status_code == 403:
|
173
|
+
raise PermissionError(
|
174
|
+
"Failed to execute. A 403 response occured. "
|
175
|
+
"This could a token issue or a url path issue "
|
176
|
+
"By default unknown gateway calls return 403 errors. "
|
177
|
+
)
|
178
|
+
elif response.status_code != 200:
|
179
|
+
raise RuntimeError(
|
180
|
+
f"Unknown Error occured during executions: {response.status_code}. "
|
181
|
+
f"Reason: {response.reason}"
|
182
|
+
)
|
183
|
+
|
184
|
+
execution_id = str(json_response.get("execution_id"))
|
185
|
+
|
186
|
+
self.log(f"\tExecution {execution_id} started.")
|
187
|
+
|
188
|
+
return json_response
|
189
|
+
|
190
|
+
def wait_for_results(
|
191
|
+
self, execution_id: str, max_wait_in_seconds: float = 900
|
192
|
+
) -> str | None:
|
193
|
+
"""
|
194
|
+
Wait for results
|
195
|
+
Args:
|
196
|
+
execution_id (str): the analysis execution id
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
str | None: on success: a url for download, on failure: None
|
200
|
+
"""
|
201
|
+
|
202
|
+
url = f"{self.endpoints.execution(execution_id)}"
|
203
|
+
|
204
|
+
headers = HttpUtilities.get_headers(self.authenticator.cognito.jwt)
|
205
|
+
current_time = datetime.now()
|
206
|
+
# Create a timedelta object representing 15 minutes
|
207
|
+
time_delta = timedelta(seconds=max_wait_in_seconds)
|
208
|
+
|
209
|
+
# Add the timedelta to the current time
|
210
|
+
max_time = current_time + time_delta
|
211
|
+
|
212
|
+
complete = False
|
213
|
+
while not complete:
|
214
|
+
response = requests.get(url, headers=headers, timeout=30)
|
215
|
+
json_response: dict = response.json()
|
216
|
+
status = json_response.get("status")
|
217
|
+
complete = status == "complete"
|
218
|
+
elapsed = (
|
219
|
+
json_response.get("times", {}).get("elapsed", "0:00:00") or "--:--"
|
220
|
+
)
|
221
|
+
if status == "failed" or complete:
|
222
|
+
break
|
223
|
+
if not complete:
|
224
|
+
self.log(f"\t\twaiting for results.... {status}: {elapsed}")
|
225
|
+
time.sleep(5)
|
226
|
+
if datetime.now() > max_time:
|
227
|
+
status = "timeout"
|
228
|
+
break
|
229
|
+
if status is None and elapsed is None:
|
230
|
+
# we have a problem
|
231
|
+
status = "unknown issue"
|
232
|
+
break
|
233
|
+
|
234
|
+
if status == "complete":
|
235
|
+
self.log("\tExecution complete.")
|
236
|
+
self.log(f"\tExecution duration = {elapsed}.")
|
237
|
+
return json_response["presigned"]["url"]
|
238
|
+
else:
|
239
|
+
raise RuntimeError(
|
240
|
+
f"\tExecution failed. Execution ID = {execution_id}. reason: {json_response.get('errors')}"
|
241
|
+
)
|
242
|
+
|
243
|
+
def download_file(
|
244
|
+
self,
|
245
|
+
presigned_download_url: str,
|
246
|
+
output_directory: str | None = None,
|
247
|
+
do_unzip: bool = False,
|
248
|
+
) -> str | None:
|
249
|
+
"""
|
250
|
+
# Step 5
|
251
|
+
Download completed analysis files
|
252
|
+
|
253
|
+
Args:
|
254
|
+
presigned_download_url (str): presigned download url
|
255
|
+
output_directory (str | None): optional output directory
|
256
|
+
|
257
|
+
Returns:
|
258
|
+
str: file path to results or None
|
259
|
+
"""
|
260
|
+
if output_directory is None:
|
261
|
+
output_directory = str(Path(__file__).parent.parent)
|
262
|
+
output_directory = os.path.join(output_directory, ".aplos-nca-output")
|
263
|
+
|
264
|
+
if EnvironmentVars.is_running_in_aws_lambda():
|
265
|
+
# /tmp is the only directory we can write to unless we mount an external drive
|
266
|
+
# TODO: allow for external mapped drives, perhaps test if we can write to it
|
267
|
+
|
268
|
+
output_directory = os.path.join("/tmp", ".aplos-nca-output")
|
269
|
+
|
270
|
+
self.log(
|
271
|
+
f"\t\tRunning in AWS Lambda. Setting output directory to {output_directory}"
|
272
|
+
)
|
273
|
+
|
274
|
+
output_file = f"results-{time.strftime('%Y-%m-%d-%Hh%Mm%Ss')}.zip"
|
275
|
+
|
276
|
+
output_file = os.path.join(output_directory, output_file)
|
277
|
+
os.makedirs(output_directory, exist_ok=True)
|
278
|
+
|
279
|
+
response = requests.get(presigned_download_url, timeout=60)
|
280
|
+
# write the zip to a file
|
281
|
+
with open(output_file, "wb") as f:
|
282
|
+
f.write(response.content)
|
283
|
+
|
284
|
+
# optionally, extract all the files from the zip
|
285
|
+
if do_unzip:
|
286
|
+
with zipfile.ZipFile(output_file, "r") as zip_ref:
|
287
|
+
zip_ref.extractall(output_file.replace(".zip", ""))
|
288
|
+
|
289
|
+
unzipped_state = "and unzipped" if do_unzip else "in zip format"
|
290
|
+
|
291
|
+
self.log(f"\tResults file downloaded {unzipped_state}.")
|
292
|
+
self.log(f"\t\tResults are available in: {output_directory}")
|
293
|
+
|
294
|
+
return output_file
|
295
|
+
|
296
|
+
def log(self, message: str | Dict[str, Any]):
|
297
|
+
"""Log the message"""
|
298
|
+
logger.debug(message)
|
299
|
+
|
300
|
+
if isinstance(message, dict):
|
301
|
+
message = json.dumps(message, indent=2)
|
302
|
+
|
303
|
+
if self.verbose:
|
304
|
+
print(message)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
"""
|
2
|
-
Copyright 2024 Aplos Analytics
|
2
|
+
Copyright 2024-2025 Aplos Analytics
|
3
3
|
All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
|
4
4
|
Property of Aplos Analytics, Utah, USA
|
5
5
|
"""
|
@@ -30,8 +30,8 @@ class NCAAppConfiguration:
|
|
30
30
|
|
31
31
|
"""
|
32
32
|
|
33
|
-
def __init__(self,
|
34
|
-
self.
|
33
|
+
def __init__(self, host: str):
|
34
|
+
self.__endpoints: NCAEndpoints = NCAEndpoints(host=host)
|
35
35
|
self.__response: requests.Response | None = None
|
36
36
|
|
37
37
|
def get(self) -> requests.Response:
|
@@ -40,7 +40,7 @@ class NCAAppConfiguration:
|
|
40
40
|
if self.__response is not None:
|
41
41
|
return self.__response
|
42
42
|
|
43
|
-
url = self.
|
43
|
+
url = self.__endpoints.app_configuration
|
44
44
|
self.__response = requests.get(url, timeout=30)
|
45
45
|
if self.__response.status_code != 200:
|
46
46
|
raise RuntimeError("App configuration url is not working.")
|
@@ -1,25 +1,25 @@
|
|
1
1
|
"""
|
2
|
-
Copyright 2024 Aplos Analytics
|
2
|
+
Copyright 2024-2025 Aplos Analytics
|
3
3
|
All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
|
4
4
|
Property of Aplos Analytics, Utah, USA
|
5
5
|
"""
|
6
6
|
|
7
7
|
from typing import Optional
|
8
|
-
from aplos_nca_saas_sdk.
|
8
|
+
from aplos_nca_saas_sdk.nca_resources.aws_cognito import CognitoAuthentication
|
9
9
|
from aplos_nca_saas_sdk.nca_resources.nca_app_configuration import (
|
10
10
|
NCAAppConfiguration,
|
11
11
|
)
|
12
12
|
|
13
13
|
|
14
|
-
class
|
15
|
-
"""NCA
|
14
|
+
class NCAAuthenticator:
|
15
|
+
"""NCA Authenticator"""
|
16
16
|
|
17
17
|
def __init__(
|
18
18
|
self,
|
19
19
|
*,
|
20
20
|
cognito_client_id: Optional[str] = None,
|
21
21
|
cognito_region: Optional[str] = None,
|
22
|
-
|
22
|
+
host: Optional[str] = None,
|
23
23
|
) -> None:
|
24
24
|
"""
|
25
25
|
NCA SaaS Login
|
@@ -27,45 +27,43 @@ class NCALogin:
|
|
27
27
|
Args:
|
28
28
|
cognito_client_id (Optional[str], optional): Cognito Client Id. Defaults to None.
|
29
29
|
cognito_region (Optional[str], optional): Cognito Region. Defaults to None.
|
30
|
-
|
30
|
+
host (Optional[str], optional): Aplos NCA SaaS host. Defaults to None.
|
31
31
|
|
32
32
|
Requirements:
|
33
33
|
Either pass in the cognito_client_id and cognito_region.
|
34
|
-
or set the
|
34
|
+
or set the host to automatically get the client_id and region.
|
35
35
|
"""
|
36
|
-
|
37
|
-
self.access_token: Optional[str] = None
|
38
|
-
self.refresh_token: Optional[str] = None
|
36
|
+
|
39
37
|
self.__cognito_client_id = cognito_client_id
|
40
38
|
self.__region = cognito_region
|
41
|
-
self.
|
42
|
-
self.__cognito: Optional[
|
39
|
+
self.__host: Optional[str] = host
|
40
|
+
self.__cognito: Optional[CognitoAuthentication] = None
|
43
41
|
self.__config: Optional[NCAAppConfiguration] = None
|
44
42
|
|
45
43
|
@property
|
46
|
-
def cognito(self) ->
|
44
|
+
def cognito(self) -> CognitoAuthentication:
|
47
45
|
"""
|
48
46
|
Cognito Authentication
|
49
47
|
Returns:
|
50
48
|
CognitoAuthenication: object to handle cognito authentication
|
51
49
|
"""
|
52
50
|
if self.__cognito is None:
|
53
|
-
self.__cognito =
|
51
|
+
self.__cognito = CognitoAuthentication(
|
54
52
|
client_id=self.__cognito_client_id,
|
55
53
|
region=self.__region,
|
56
|
-
aplos_domain=self.
|
54
|
+
aplos_domain=self.__host,
|
57
55
|
)
|
58
56
|
|
59
57
|
return self.__cognito
|
60
58
|
|
61
59
|
@property
|
62
|
-
def
|
60
|
+
def host(self) -> str | None:
|
63
61
|
"""
|
64
62
|
Domain
|
65
63
|
Returns:
|
66
|
-
str: the
|
64
|
+
str: the host
|
67
65
|
"""
|
68
|
-
return self.
|
66
|
+
return self.__host
|
69
67
|
|
70
68
|
@property
|
71
69
|
def config(self) -> NCAAppConfiguration:
|
@@ -75,13 +73,13 @@ class NCALogin:
|
|
75
73
|
NCAAppConfiguration: object to handle the NCA App Configuration
|
76
74
|
"""
|
77
75
|
if self.__config is None:
|
78
|
-
if self.
|
76
|
+
if self.__host is None:
|
79
77
|
raise RuntimeError(
|
80
78
|
"Failed to get Aplos Configuration. The Domain is not set."
|
81
79
|
)
|
82
80
|
|
83
81
|
self.__config = NCAAppConfiguration(
|
84
|
-
|
82
|
+
host=self.__host,
|
85
83
|
)
|
86
84
|
|
87
85
|
return self.__config
|
@@ -96,9 +94,14 @@ class NCALogin:
|
|
96
94
|
Args:
|
97
95
|
username (str): the username
|
98
96
|
password (str): the users password
|
99
|
-
|
97
|
+
Returns:
|
98
|
+
str: JWT (JSON Web Token)
|
100
99
|
"""
|
100
|
+
if not username:
|
101
|
+
raise ValueError("Missing username. Please provide a valid username.")
|
102
|
+
if not password:
|
103
|
+
raise ValueError("Missing password. Please provide a valid password.")
|
101
104
|
|
102
|
-
self.
|
105
|
+
self.cognito.login(username=username, password=password)
|
103
106
|
|
104
|
-
return self.jwt
|
107
|
+
return self.cognito.jwt
|
@@ -1,5 +1,5 @@
|
|
1
1
|
"""
|
2
|
-
Copyright 2024 Aplos Analytics
|
2
|
+
Copyright 2024-2025 Aplos Analytics
|
3
3
|
All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
|
4
4
|
Property of Aplos Analytics, Utah, USA
|
5
5
|
"""
|
@@ -8,51 +8,77 @@ Property of Aplos Analytics, Utah, USA
|
|
8
8
|
class NCAEndpoints:
|
9
9
|
"""Aplos NCA SaaS Endpoints"""
|
10
10
|
|
11
|
-
def __init__(
|
12
|
-
self
|
11
|
+
def __init__(
|
12
|
+
self, *, host: str, tenant_id: str | None = None, user_id: str | None = None
|
13
|
+
):
|
14
|
+
self.__host: str = host
|
13
15
|
self.__protocal: str = "https://"
|
16
|
+
self.tenant_id: str | None = tenant_id
|
17
|
+
self.user_id: str | None = user_id
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
@property
|
20
|
+
def origin(self) -> str:
|
21
|
+
"""The origin path e.g. https://api.aplos-nca.com"""
|
22
|
+
base = f"{self.__protocal}{self.__host}"
|
23
|
+
return base
|
18
24
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
if not tenant_id:
|
23
|
-
raise ValueError("Tenant ID is required on the users path")
|
24
|
-
route = f"{route}/users/{user_id}"
|
25
|
+
@property
|
26
|
+
def tenant_path(self) -> str:
|
27
|
+
"""Returns the tenant path"""
|
25
28
|
|
26
|
-
|
29
|
+
if not self.tenant_id:
|
30
|
+
raise ValueError("Missing Tenant Id")
|
27
31
|
|
28
|
-
|
32
|
+
return f"{self.origin}/tenants/{self.tenant_id}"
|
33
|
+
|
34
|
+
@property
|
35
|
+
def user_path(self) -> str:
|
36
|
+
"""Returns the user path"""
|
37
|
+
|
38
|
+
if not self.user_id:
|
39
|
+
raise ValueError("Missing User Id")
|
40
|
+
return f"{self.tenant_path}/users/{self.user_id}"
|
41
|
+
|
42
|
+
@property
|
43
|
+
def tenant(self) -> str:
|
29
44
|
"""Returns the tenant endpoint"""
|
30
|
-
return f"{self.
|
45
|
+
return f"{self.tenant_path}"
|
31
46
|
|
47
|
+
@property
|
32
48
|
def app_configuration(self) -> str:
|
33
|
-
"""
|
34
|
-
|
49
|
+
"""
|
50
|
+
Returns the configuration endpoint. This is a public endpoint.
|
51
|
+
"""
|
52
|
+
return f"{self.origin}/app/configuration"
|
35
53
|
|
36
|
-
|
54
|
+
@property
|
55
|
+
def user(self) -> str:
|
37
56
|
"""Returns the user endpoint"""
|
38
|
-
return f"{self.
|
57
|
+
return f"{self.user_path}"
|
39
58
|
|
40
|
-
|
59
|
+
@property
|
60
|
+
def executions(self) -> str:
|
41
61
|
"""Returns the executions endpoint"""
|
42
|
-
return f"{self.
|
62
|
+
return f"{self.user_path}/nca/executions"
|
43
63
|
|
44
|
-
def execution(self,
|
64
|
+
def execution(self, execution_id: str) -> str:
|
45
65
|
"""Returns the executions endpoint"""
|
46
|
-
return f"{self.executions
|
66
|
+
return f"{self.executions}/{execution_id}"
|
47
67
|
|
48
|
-
|
68
|
+
@property
|
69
|
+
def validations(self) -> str:
|
70
|
+
"""Returns the validations endpoint"""
|
71
|
+
return f"{self.user_path}/nca/validations"
|
72
|
+
|
73
|
+
@property
|
74
|
+
def files(self) -> str:
|
49
75
|
"""Returns the files endpoint"""
|
50
|
-
return f"{self.
|
76
|
+
return f"{self.user_path}/nca/files"
|
51
77
|
|
52
|
-
def file(self,
|
78
|
+
def file(self, file_id: str) -> str:
|
53
79
|
"""Returns the file endpoint"""
|
54
|
-
return f"{self.files
|
55
|
-
|
56
|
-
def file_data(self,
|
80
|
+
return f"{self.files}/{file_id}"
|
81
|
+
|
82
|
+
def file_data(self, file_id: str) -> str:
|
57
83
|
"""Returns get file data endpoint"""
|
58
|
-
return f"{self.
|
84
|
+
return f"{self.files}/{file_id}/data"
|