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.
Files changed (49) hide show
  1. aplos_nca_saas_sdk/integration_testing/configs/_config_base.py +7 -2
  2. aplos_nca_saas_sdk/integration_testing/configs/app_settings_config.py +88 -0
  3. aplos_nca_saas_sdk/integration_testing/configs/config_sample.json +31 -18
  4. aplos_nca_saas_sdk/integration_testing/configs/file_upload_config.py +94 -0
  5. aplos_nca_saas_sdk/integration_testing/configs/{login.py → login_config.py} +36 -23
  6. aplos_nca_saas_sdk/integration_testing/configs/nca_execution_config.py +186 -0
  7. aplos_nca_saas_sdk/integration_testing/files/executions/config1.json +46 -0
  8. aplos_nca_saas_sdk/integration_testing/integration_test_base.py +10 -3
  9. aplos_nca_saas_sdk/integration_testing/integration_test_configurations.py +15 -7
  10. aplos_nca_saas_sdk/integration_testing/integration_test_factory.py +1 -1
  11. aplos_nca_saas_sdk/integration_testing/integration_test_response.py +1 -1
  12. aplos_nca_saas_sdk/integration_testing/integration_test_suite.py +10 -3
  13. aplos_nca_saas_sdk/integration_testing/main.py +6 -6
  14. aplos_nca_saas_sdk/integration_testing/readme.md +1 -1
  15. aplos_nca_saas_sdk/integration_testing/tests/app_configuration_test.py +3 -3
  16. aplos_nca_saas_sdk/integration_testing/tests/app_login_test.py +4 -4
  17. aplos_nca_saas_sdk/integration_testing/tests/file_upload_test.py +44 -92
  18. aplos_nca_saas_sdk/integration_testing/tests/nca_analysis_test.py +89 -0
  19. aplos_nca_saas_sdk/integration_testing/tests/{app_execution_test.py → validation_test.py} +1 -1
  20. aplos_nca_saas_sdk/nca_resources/_api_base.py +44 -0
  21. aplos_nca_saas_sdk/{aws_resources → nca_resources}/aws_cognito.py +13 -7
  22. aplos_nca_saas_sdk/{aws_resources → nca_resources}/aws_s3_presigned_payload.py +3 -3
  23. aplos_nca_saas_sdk/{aws_resources → nca_resources}/aws_s3_presigned_upload.py +28 -22
  24. aplos_nca_saas_sdk/nca_resources/nca_analysis.py +304 -0
  25. aplos_nca_saas_sdk/nca_resources/nca_app_configuration.py +4 -4
  26. aplos_nca_saas_sdk/nca_resources/{nca_login.py → nca_authenticator.py} +26 -23
  27. aplos_nca_saas_sdk/nca_resources/nca_endpoints.py +56 -30
  28. aplos_nca_saas_sdk/nca_resources/nca_file_download.py +130 -0
  29. aplos_nca_saas_sdk/nca_resources/nca_file_upload.py +27 -29
  30. aplos_nca_saas_sdk/nca_resources/nca_validations.py +34 -0
  31. aplos_nca_saas_sdk/run_analysis_execution.py +148 -0
  32. aplos_nca_saas_sdk/utilities/commandline_args.py +32 -38
  33. aplos_nca_saas_sdk/utilities/environment_services.py +3 -2
  34. aplos_nca_saas_sdk/utilities/environment_vars.py +17 -4
  35. aplos_nca_saas_sdk/utilities/file_utility.py +33 -0
  36. aplos_nca_saas_sdk/utilities/http_utility.py +5 -14
  37. aplos_nca_saas_sdk/version.py +1 -1
  38. {aplos_nca_saas_sdk-0.0.11.dist-info → aplos_nca_saas_sdk-0.0.13.dist-info}/METADATA +1 -1
  39. aplos_nca_saas_sdk-0.0.13.dist-info/RECORD +51 -0
  40. {aplos_nca_saas_sdk-0.0.11.dist-info → aplos_nca_saas_sdk-0.0.13.dist-info}/licenses/LICENSE +1 -1
  41. aplos_nca_saas_sdk/integration_testing/configs/app_settings.py +0 -88
  42. aplos_nca_saas_sdk/integration_testing/configs/file_upload.py +0 -104
  43. aplos_nca_saas_sdk/integration_testing/tests/app_validation_test.py +0 -5
  44. aplos_nca_saas_sdk/nca_resources/nca_executions.py +0 -387
  45. aplos_nca_saas_sdk-0.0.11.dist-info/RECORD +0 -44
  46. /aplos_nca_saas_sdk/{files/analysis_files/single_ev/configuration_single_ev.json → sample_files/analysis_files/single_ev/config.json} +0 -0
  47. /aplos_nca_saas_sdk/{files/analysis_files/single_ev/single_ev.csv → sample_files/analysis_files/single_ev/input.csv} +0 -0
  48. /aplos_nca_saas_sdk/{files → sample_files}/analysis_files/single_ev/meta_data.json +0 -0
  49. {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.aws_resources.aws_s3_presigned_payload import (
13
- S3PresignedPayload,
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, Routes
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 S3PresignedUpload:
19
- """S3PresignedUpload"""
19
+ class S3PresignedUrlUpload(NCAApiBaseClass):
20
+ """S3PresignedUrlUpload"""
20
21
 
21
- def __init__(self, jwt: str, api_url: str) -> None:
22
- self.api_url = api_url
23
- self.jwt = jwt
22
+ def __init__(self, host: str) -> None:
23
+ super().__init__(host)
24
24
 
25
- def upload_file(self, input_file: str) -> Dict[str, Any]:
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: S3PresignedPayload = self.__get_presigned_upload_info(
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(self, input_file: str) -> S3PresignedPayload:
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
- S3PresignedPayload: instance of S3PresignedPayload
58
+ S3PresignedUrlPayload: instance of S3PresignedUrlPayload
54
59
  """
55
60
 
56
- url = f"{self.api_url}/{Routes.NCA_GENERATE_UPLOAD}"
57
- headers = HttpUtilities.get_headers(self.jwt)
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: S3PresignedPayload = S3PresignedPayload(result)
84
+ payload: S3PresignedUrlPayload = S3PresignedUrlPayload(result)
80
85
 
81
86
  return payload
82
87
 
83
88
  def __upload_file_to_s3(
84
- self, payload: S3PresignedPayload, input_file: str
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 (S3PresignedPayload): instance of S3PresignedPayload with all the data needed
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=30
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, aplos_saas_domain: str):
34
- self.endpoints: NCAEndpoints = NCAEndpoints(aplos_saas_domain=aplos_saas_domain)
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.endpoints.app_configuration()
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.aws_resources.aws_cognito import CognitoAuthenication
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 NCALogin:
15
- """NCA Login"""
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
- aplos_saas_domain: Optional[str] = None,
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
- aplos_saas_domain (Optional[str], optional): Aplos NCA SaaS domain. Defaults to None.
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 aplos_saas_domain to automatically get the client_id and region.
34
+ or set the host to automatically get the client_id and region.
35
35
  """
36
- self.jwt: str
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.__domain: Optional[str] = aplos_saas_domain
42
- self.__cognito: Optional[CognitoAuthenication] = None
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) -> CognitoAuthenication:
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 = CognitoAuthenication(
51
+ self.__cognito = CognitoAuthentication(
54
52
  client_id=self.__cognito_client_id,
55
53
  region=self.__region,
56
- aplos_domain=self.__domain,
54
+ aplos_domain=self.__host,
57
55
  )
58
56
 
59
57
  return self.__cognito
60
58
 
61
59
  @property
62
- def domain(self) -> str | None:
60
+ def host(self) -> str | None:
63
61
  """
64
62
  Domain
65
63
  Returns:
66
- str: the domain
64
+ str: the host
67
65
  """
68
- return self.__domain
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.__domain is None:
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
- aplos_saas_domain=self.__domain,
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.jwt = self.cognito.login(username=username, password=password)
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__(self, *, aplos_saas_domain: str):
12
- self.__domain: str = aplos_saas_domain
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
- def __base(self, tenant_id: str | None = None, user_id: str | None = None) -> str:
16
- """Returns the base endpoint"""
17
- route = f"{self.__protocal}{self.__domain}"
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
- if tenant_id:
20
- route = f"{route}/tenants/{tenant_id}"
21
- if user_id:
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
- return route
29
+ if not self.tenant_id:
30
+ raise ValueError("Missing Tenant Id")
27
31
 
28
- def tenant(self, tenant_id: str) -> str:
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.__base(tenant_id=tenant_id)}"
45
+ return f"{self.tenant_path}"
31
46
 
47
+ @property
32
48
  def app_configuration(self) -> str:
33
- """Returns the configuration endpoint"""
34
- return f"{self.__base()}/app/configuration"
49
+ """
50
+ Returns the configuration endpoint. This is a public endpoint.
51
+ """
52
+ return f"{self.origin}/app/configuration"
35
53
 
36
- def user(self, tenant_id: str, user_id: str) -> str:
54
+ @property
55
+ def user(self) -> str:
37
56
  """Returns the user endpoint"""
38
- return f"{self.__base(tenant_id=tenant_id, user_id=user_id)}"
57
+ return f"{self.user_path}"
39
58
 
40
- def executions(self, tenant_id: str, user_id: str) -> str:
59
+ @property
60
+ def executions(self) -> str:
41
61
  """Returns the executions endpoint"""
42
- return f"{self.__base(tenant_id=tenant_id, user_id=user_id)}/nca/executions"
62
+ return f"{self.user_path}/nca/executions"
43
63
 
44
- def execution(self, tenant_id: str, user_id: str, execution_id: str) -> str:
64
+ def execution(self, execution_id: str) -> str:
45
65
  """Returns the executions endpoint"""
46
- return f"{self.executions(tenant_id=tenant_id, user_id=user_id)}/{execution_id}"
66
+ return f"{self.executions}/{execution_id}"
47
67
 
48
- def files(self, tenant_id: str, user_id: str) -> str:
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.__base(tenant_id=tenant_id, user_id=user_id)}/nca/files"
76
+ return f"{self.user_path}/nca/files"
51
77
 
52
- def file(self, tenant_id: str, user_id: str, file_id: str) -> str:
78
+ def file(self, file_id: str) -> str:
53
79
  """Returns the file endpoint"""
54
- return f"{self.files(tenant_id=tenant_id, user_id=user_id)}/{file_id}"
55
-
56
- def file_data(self, tenant_id: str, user_id: str, file_id: str) -> str:
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.__base(tenant_id=tenant_id, user_id=user_id)}/nca/files/{file_id}/data"
84
+ return f"{self.files}/{file_id}/data"