aplos-nca-saas-sdk 1.0.0__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 (28) hide show
  1. aplos_nca_saas_sdk/__init__.py +0 -0
  2. aplos_nca_saas_sdk/aws_resources/aws_cognito.py +188 -0
  3. aplos_nca_saas_sdk/aws_resources/aws_s3_presigned_payload.py +49 -0
  4. aplos_nca_saas_sdk/aws_resources/aws_s3_presigned_upload.py +116 -0
  5. aplos_nca_saas_sdk/files/analysis_files/single_ev/configuration_single_ev.json +51 -0
  6. aplos_nca_saas_sdk/files/analysis_files/single_ev/meta_data.json +17 -0
  7. aplos_nca_saas_sdk/files/analysis_files/single_ev/single_ev.csv +121 -0
  8. aplos_nca_saas_sdk/integration_testing/integration_test_base.py +34 -0
  9. aplos_nca_saas_sdk/integration_testing/integration_test_factory.py +62 -0
  10. aplos_nca_saas_sdk/integration_testing/integration_test_suite.py +84 -0
  11. aplos_nca_saas_sdk/integration_testing/main.py +28 -0
  12. aplos_nca_saas_sdk/integration_testing/readme.md +18 -0
  13. aplos_nca_saas_sdk/integration_testing/tests/app_configuration_test.py +30 -0
  14. aplos_nca_saas_sdk/integration_testing/tests/app_execution_test.py +5 -0
  15. aplos_nca_saas_sdk/integration_testing/tests/app_login_test.py +32 -0
  16. aplos_nca_saas_sdk/integration_testing/tests/app_validation_test.py +5 -0
  17. aplos_nca_saas_sdk/nca_resources/nca_app_configuration.py +69 -0
  18. aplos_nca_saas_sdk/nca_resources/nca_endpoints.py +54 -0
  19. aplos_nca_saas_sdk/nca_resources/nca_executions.py +378 -0
  20. aplos_nca_saas_sdk/nca_resources/nca_login.py +104 -0
  21. aplos_nca_saas_sdk/utilities/commandline_args.py +332 -0
  22. aplos_nca_saas_sdk/utilities/environment_services.py +81 -0
  23. aplos_nca_saas_sdk/utilities/environment_vars.py +23 -0
  24. aplos_nca_saas_sdk/utilities/http_utility.py +30 -0
  25. aplos_nca_saas_sdk/version.py +4 -0
  26. aplos_nca_saas_sdk-1.0.0.dist-info/METADATA +195 -0
  27. aplos_nca_saas_sdk-1.0.0.dist-info/RECORD +28 -0
  28. aplos_nca_saas_sdk-1.0.0.dist-info/WHEEL +4 -0
File without changes
@@ -0,0 +1,188 @@
1
+ """
2
+ Copyright 2024 Aplos Analytics
3
+ All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
4
+ Property of Aplos Analytics, Utah, USA
5
+ """
6
+
7
+ import boto3
8
+ import jwt as jwt_lib
9
+ from typing import Optional
10
+ from aplos_nca_saas_sdk.nca_resources.nca_app_configuration import (
11
+ NCAAppConfiguration,
12
+ )
13
+
14
+
15
+ class CognitoAuthenication:
16
+ """
17
+ Cognito Authentication
18
+
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ *,
24
+ client_id: Optional[str] = None,
25
+ region: Optional[str] = None,
26
+ aplos_domain: Optional[str] = None,
27
+ ) -> None:
28
+ # setup the client id
29
+ self.__client_id: Optional[str] = client_id
30
+ self.__jwt: Optional[str] = None
31
+ self.__access_token: Optional[str] = None
32
+ self.__refresh_token: Optional[str] = None
33
+ self.__region: str = region or "us-east-1"
34
+ self.__client: Optional[boto3.client] = None
35
+ self.__user_id: Optional[str] = None
36
+ self.__tenant_id: Optional[str] = None
37
+ self.__config: Optional[NCAAppConfiguration] = None
38
+ self.__aplos_domain: Optional[str] = aplos_domain
39
+
40
+ self.__validate_parameters()
41
+
42
+ @property
43
+ def client(self) -> boto3.client:
44
+ """
45
+ Get the boto3 client
46
+
47
+ Returns:
48
+ boto3.client: the boto3 client
49
+ """
50
+ if self.__client is None:
51
+ self.__client = boto3.client("cognito-idp", region_name=self.region)
52
+
53
+ return self.__client
54
+
55
+ @property
56
+ def client_id(self) -> str:
57
+ """
58
+ Client Id
59
+ Returns:
60
+ str: the client id
61
+ """
62
+ return self.__client_id
63
+
64
+ @property
65
+ def region(self) -> str:
66
+ """
67
+ Region
68
+ Returns:
69
+ str: the region
70
+ """
71
+ return self.__region
72
+
73
+ def __validate_parameters(self) -> None:
74
+ if self.__client_id is None and self.__aplos_domain is not None:
75
+ self.__config = NCAAppConfiguration(aplos_saas_domain=self.__aplos_domain)
76
+ self.__client_id = self.__config.cognito_client_id
77
+ self.__region = self.__config.cognito_region
78
+
79
+ if self.__client_id is None:
80
+ raise RuntimeError(
81
+ "Missing Cognito Client Id. "
82
+ "Pass in a client_id as a command arg or set the COGNITO_CLIENT_ID enviornment var. "
83
+ "Alternatively, set the aplos_domain to automatically get the client_id and region."
84
+ )
85
+
86
+ if self.__region is None:
87
+ raise RuntimeError(
88
+ "Missing Cognito Region"
89
+ "Pass in a region as a command arg or set the COGNITO_REGION enviornment var. "
90
+ "Alternatively, set the aplos_domain to automatically get the client_id and region."
91
+ )
92
+
93
+ def login(self, username: str, password: str) -> str:
94
+ """
95
+ Get a JWT (JSON Web Token)
96
+
97
+ Args:
98
+ username (str): username (email address)
99
+ password (str): password
100
+ client_id (str): cognito client/application id
101
+
102
+ Returns:
103
+ str | None: json web token (jwt)
104
+ """
105
+
106
+ if not self.client_id:
107
+ raise RuntimeError("Missing Cognito Client Id")
108
+
109
+ auth_response = self.client.initiate_auth(
110
+ ClientId=self.client_id,
111
+ # user USER_PASSWORD_AUTH flow for this type of login
112
+ AuthFlow="USER_PASSWORD_AUTH",
113
+ AuthParameters={"USERNAME": username, "PASSWORD": password},
114
+ )
115
+
116
+ if "ChallengeName" in auth_response:
117
+ # depending on your setup, it's possible you will get challenged for a
118
+ # password change. contact support if this happens
119
+ raise RuntimeError(
120
+ "New password required before a token can be provided. Please contact support or your Aplos Administrator."
121
+ )
122
+
123
+ # Extract the session tokens
124
+ # id_token is the JWT token
125
+ # typical tokens last for 30 minutes to 1 hour by default
126
+ self.__jwt = auth_response["AuthenticationResult"]["IdToken"]
127
+ # access token is if you have direct access to aws resources
128
+ # you probably won't ever need this
129
+ self.__access_token = auth_response["AuthenticationResult"]["AccessToken"] # noqa: F814, F841, pylint: disable=W0612
130
+ # refresh token if needed
131
+ # you can use refresh tokens to "refresh" your jwt or simply login again
132
+ # refresh tokens are typically good for 30 days by default
133
+ self.__refresh_token = auth_response["AuthenticationResult"]["RefreshToken"] # noqa: F814, F841, pylint: disable=w0612
134
+
135
+ # return the jwt token
136
+ if isinstance(self.__jwt, str):
137
+ self.__parse_jwt(self.__jwt)
138
+ return self.__jwt
139
+
140
+ raise RuntimeError("Failed to get a JWT token")
141
+
142
+ def __parse_jwt(self, encoded_jwt: str) -> None:
143
+ # Decode the payload (second part) from Base64
144
+ decoded_jwt: dict = jwt_lib.decode(
145
+ encoded_jwt, options={"verify_signature": False}
146
+ )
147
+ self.__user_id = decoded_jwt.get("custom:aplos_user_id")
148
+ self.__tenant_id = decoded_jwt.get("custom:aplos_user_tenant_id")
149
+
150
+ @property
151
+ def jwt(self) -> str:
152
+ """Get the JWT JSON Web Token"""
153
+ if isinstance(self.__jwt, str):
154
+ return self.__jwt
155
+
156
+ raise RuntimeError("Failed to get a JWT token")
157
+
158
+ @property
159
+ def user_id(self) -> str:
160
+ """Get the authenticated User Id"""
161
+ if isinstance(self.__user_id, str):
162
+ return self.__user_id
163
+
164
+ raise RuntimeError("Failed to get a user id")
165
+
166
+ @property
167
+ def tenant_id(self) -> str:
168
+ """Get the authenticated Tenant Id"""
169
+ if isinstance(self.__tenant_id, str):
170
+ return self.__tenant_id
171
+
172
+ raise RuntimeError("Failed to get a tenant id")
173
+
174
+ @property
175
+ def access_token(self) -> str:
176
+ """Get the AWS Access Token"""
177
+ if isinstance(self.__access_token, str):
178
+ return self.__access_token
179
+
180
+ raise RuntimeError("Failed to get an access token")
181
+
182
+ @property
183
+ def refresh_token(self) -> str:
184
+ """Get the AWS Cognito Refresh Token"""
185
+ if isinstance(self.__refresh_token, str):
186
+ return self.__refresh_token
187
+
188
+ raise RuntimeError("Failed to get a refresh token")
@@ -0,0 +1,49 @@
1
+ """
2
+ Copyright 2024 Aplos Analytics
3
+ All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
4
+ Property of Aplos Analytics, Utah, USA
5
+ """
6
+
7
+
8
+ class S3PresignedPayload:
9
+ """S3PresignedPayload"""
10
+
11
+ def __init__(self, payload: dict | None = None) -> None:
12
+ self.url: str | None = None
13
+ self.form_data: dict = {}
14
+ self.raw_payload: dict | None = payload
15
+ self.file_id: str | None = None
16
+ if payload:
17
+ self.__load(payload)
18
+
19
+ def __load(self, payload: dict):
20
+ if "presigned" not in payload:
21
+ raise KeyError("Missing key 'presigned' in response payload.")
22
+ self.url = payload["presigned"]["url"]
23
+ self.form_data = self.__get_presigned_form_data(payload=payload)
24
+
25
+ filed_id = payload.get("file", {}).get("id", None)
26
+ if not filed_id:
27
+ raise KeyError("Missing key 'file.id' in response payload.")
28
+ self.file_id = filed_id
29
+
30
+ def __get_presigned_form_data(self, payload: dict) -> dict:
31
+ """
32
+ Generates the form data need for an s3 presigned url upload
33
+ NOTE: the order of the items below are important. s3 presigned url uploads are
34
+ very picky in the way the process the data. if they are out of order you may get
35
+ errors that the "keys" are not present. It's best to centralize this into a library
36
+ """
37
+ form_data = {
38
+ "key": payload["presigned"]["fields"]["key"],
39
+ "x-amz-algorithm": payload["presigned"]["fields"]["x-amz-algorithm"],
40
+ "x-amz-credential": payload["presigned"]["fields"]["x-amz-credential"],
41
+ "x-amz-date": payload["presigned"]["fields"]["x-amz-date"],
42
+ "x-amz-security-token": payload["presigned"]["fields"][
43
+ "x-amz-security-token"
44
+ ],
45
+ "policy": payload["presigned"]["fields"]["policy"],
46
+ "x-amz-signature": payload["presigned"]["fields"]["x-amz-signature"],
47
+ }
48
+
49
+ return form_data
@@ -0,0 +1,116 @@
1
+ """
2
+ Copyright 2024 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
+
10
+ import requests
11
+ from aplos_nca_saas_sdk.aws_resources.aws_s3_presigned_payload import (
12
+ S3PresignedPayload,
13
+ )
14
+ from aplos_nca_saas_sdk.utilities.http_utility import HttpUtilities, Routes
15
+
16
+
17
+ class S3PresignedUpload:
18
+ """S3PresignedUpload"""
19
+
20
+ def __init__(self, jwt: str, api_url: str) -> None:
21
+ self.api_url = api_url
22
+ self.jwt = jwt
23
+
24
+ def upload_file(self, input_file: str) -> S3PresignedPayload:
25
+ """
26
+ Uploads a file to your Aplos Cloud Account in AWS
27
+
28
+ Args:
29
+ input_file (str): path to the analysis file you are uploading
30
+
31
+ Returns:
32
+ S3PresignedPayload: an instance of S3PresignedPayload
33
+ """
34
+
35
+ # get the presigned url for uploading
36
+ paylod: S3PresignedPayload = self.__get_presigned_upload_info(
37
+ input_file=input_file
38
+ )
39
+ # upload the files
40
+ self.__upload_file_to_s3(paylod, input_file=input_file)
41
+
42
+ return paylod
43
+
44
+ def __get_presigned_upload_info(self, input_file: str) -> S3PresignedPayload:
45
+ """
46
+ Performs all the necessary steps for creating a presigned url to upload a file to S3.
47
+ We're using AWS S3 presigned urls for security as well as allowing for very large files if requried.
48
+ Args:
49
+ input_file (str): the path to the input (analysis) file
50
+
51
+ Returns:
52
+ S3PresignedPayload: instance of S3PresignedPayload
53
+ """
54
+
55
+ url = f"{self.api_url}/{Routes.NCA_GENERATE_UPLOAD}"
56
+ headers = HttpUtilities.get_headers(self.jwt)
57
+
58
+ body = {"file_name": input_file, "method_type": "post"}
59
+ response = requests.post(
60
+ url=url, headers=headers, data=json.dumps(body), timeout=30
61
+ )
62
+
63
+ if response.status_code == 403:
64
+ raise PermissionError(
65
+ "Failed to get a presigned url. "
66
+ f"Status Code: {response.status_code }"
67
+ f"Reason: {response.reason} "
68
+ f"403 Errors can also occur if you have an invalid path."
69
+ )
70
+ elif response.status_code != 200:
71
+ raise RuntimeError(
72
+ "Failed to get a presigned url. "
73
+ f"Status Code: {response.status_code }"
74
+ f"Reason: {response.reason}"
75
+ )
76
+ result = response.json()
77
+
78
+ payload: S3PresignedPayload = S3PresignedPayload(result)
79
+
80
+ return payload
81
+
82
+ def __upload_file_to_s3(self, payload: S3PresignedPayload, input_file: str) -> bool:
83
+ """
84
+ Peforms the actual uploading via a presigned url for S3 bucket storage
85
+ Args:
86
+ payload (S3PresignedPayload): instance of S3PresignedPayload with all the data needed
87
+ input_file (str): the path to a file being uploaded
88
+
89
+ Raises:
90
+ FileNotFoundError: If the file is not found
91
+
92
+ Returns:
93
+ bool: True on success, False if not
94
+ """
95
+
96
+ if not os.path.exists(input_file):
97
+ raise FileNotFoundError(
98
+ "The input file you are submitting cannot be found. Please check the path and try again."
99
+ )
100
+
101
+ with open(input_file, "rb") as file:
102
+ files = {"file": (input_file, file)}
103
+ # upload to s3 with the presigned url
104
+ upload_response = requests.post(
105
+ str(payload.url), data=payload.form_data, files=files, timeout=30
106
+ )
107
+
108
+ # Check the response: 204 is a success in this case
109
+ if upload_response and upload_response.status_code == 204:
110
+ return True
111
+ else:
112
+ raise RuntimeError(
113
+ "Error uploading the file. "
114
+ f"Status Code: {upload_response.status_code}"
115
+ f"Response: {upload_response.reason}"
116
+ )
@@ -0,0 +1,51 @@
1
+ {
2
+ "version": "2024-02-22",
3
+ "designations": {
4
+ "blq": "BLQ",
5
+ "missing": "NS",
6
+ "concentration_unit": "ng/mL"
7
+ },
8
+ "columns": {
9
+ "time": "time",
10
+ "unique_id": "subject",
11
+ "concentration": {
12
+ "observed": "conc.obs",
13
+ "analyzed": "conc"
14
+ },
15
+ "sorting": [
16
+ "subject",
17
+ "time"
18
+ ],
19
+ "grouping": [
20
+ "subject"
21
+ ]
22
+ },
23
+ "dosing": {
24
+ "amount": 10000,
25
+ "unit": "mg",
26
+ "frequency": "single",
27
+ "type": "ev"
28
+ },
29
+ "time": {
30
+ "unit": "hr",
31
+ "time_of_administration": 0,
32
+ "tau": 24,
33
+ "infusion": {
34
+ "protocol": "rate",
35
+ "amount": "1"
36
+ }
37
+ },
38
+ "kel_rules": {
39
+ "regression_statistic": "adj_r2",
40
+ "minimum_statistic": 0,
41
+ "maximum_extrapolation_linear": 0,
42
+ "maximum_extrapolation_logarithmic": 0,
43
+ "maximum_time_points": 0,
44
+ "minimum_span": 0,
45
+ "earliest_time_point": 0,
46
+ "tie": [
47
+ "kel_n",
48
+ "kel_lower"
49
+ ]
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "description": "meta data is optional. contains any json or string you'd like. json will give you structure. example below",
3
+ "reason": "analysis for xyz",
4
+ "team": [
5
+ {
6
+ "name": "john doe",
7
+ "title": "lead analyst",
8
+ "company": "little pharma"
9
+ },
10
+ {
11
+ "name": "jane doe",
12
+ "title": "consultant",
13
+ "company": "acme consulting"
14
+ }
15
+ ]
16
+
17
+ }
@@ -0,0 +1,121 @@
1
+ subject,sex,dose,time,conc,conc.obs,Dose.type,Dose.amt,Dose.unit,Dose.time,conc.unit,time.unit
2
+ 1,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
3
+ 1,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
4
+ 1,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
5
+ 1,F,10000,1.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
6
+ 1,F,10000,2,534,534,extravascular,10000,mg,0,mg/L,hr
7
+ 1,F,10000,4,540,540,extravascular,10000,mg,0,mg/L,hr
8
+ 1,F,10000,6,382,382,extravascular,10000,mg,0,mg/L,hr
9
+ 1,F,10000,8,268,268,extravascular,10000,mg,0,mg/L,hr
10
+ 1,F,10000,12,132,132,extravascular,10000,mg,0,mg/L,hr
11
+ 1,F,10000,16,65.2,65.2,extravascular,10000,mg,0,mg/L,hr
12
+ 1,F,10000,20,32.1,32.1,extravascular,10000,mg,0,mg/L,hr
13
+ 1,F,10000,24,15.8,15.8,extravascular,10000,mg,0,mg/L,hr
14
+ 2,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
15
+ 2,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
16
+ 2,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
17
+ 2,F,10000,1.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
18
+ 2,F,10000,2,144,144,extravascular,10000,mg,0,mg/L,hr
19
+ 2,F,10000,4,231,231,extravascular,10000,mg,0,mg/L,hr
20
+ 2,F,10000,6,197,197,extravascular,10000,mg,0,mg/L,hr
21
+ 2,F,10000,8,168,168,extravascular,10000,mg,0,mg/L,hr
22
+ 2,F,10000,12,122,122,extravascular,10000,mg,0,mg/L,hr
23
+ 2,F,10000,16,88.2,88.2,extravascular,10000,mg,0,mg/L,hr
24
+ 2,F,10000,20,64,64,extravascular,10000,mg,0,mg/L,hr
25
+ 2,F,10000,24,46.4,46.4,extravascular,10000,mg,0,mg/L,hr
26
+ 3,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
27
+ 3,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
28
+ 3,F,10000,1,78.6,78.6,extravascular,10000,mg,0,mg/L,hr
29
+ 3,F,10000,1.5,713,713,extravascular,10000,mg,0,mg/L,hr
30
+ 3,F,10000,2,842,842,extravascular,10000,mg,0,mg/L,hr
31
+ 3,F,10000,4,692,692,extravascular,10000,mg,0,mg/L,hr
32
+ 3,F,10000,6,474,474,extravascular,10000,mg,0,mg/L,hr
33
+ 3,F,10000,8,321,321,extravascular,10000,mg,0,mg/L,hr
34
+ 3,F,10000,12,147,147,extravascular,10000,mg,0,mg/L,hr
35
+ 3,F,10000,16,67.7,67.7,extravascular,10000,mg,0,mg/L,hr
36
+ 3,F,10000,20,31,31,extravascular,10000,mg,0,mg/L,hr
37
+ 3,F,10000,24,14.2,14.2,extravascular,10000,mg,0,mg/L,hr
38
+ 4,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
39
+ 4,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
40
+ 4,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
41
+ 4,F,10000,1.5,1640,1640,extravascular,10000,mg,0,mg/L,hr
42
+ 4,F,10000,2,1570,1570,extravascular,10000,mg,0,mg/L,hr
43
+ 4,F,10000,4,531,531,extravascular,10000,mg,0,mg/L,hr
44
+ 4,F,10000,6,154,154,extravascular,10000,mg,0,mg/L,hr
45
+ 4,F,10000,8,44.4,44.4,extravascular,10000,mg,0,mg/L,hr
46
+ 4,F,10000,12,3.69,3.69,extravascular,10000,mg,0,mg/L,hr
47
+ 4,F,10000,16,0.307,0.307,extravascular,10000,mg,0,mg/L,hr
48
+ 4,F,10000,20,,BLQ,extravascular,10000,mg,0,mg/L,hr
49
+ 4,F,10000,24,,BLQ,extravascular,10000,mg,0,mg/L,hr
50
+ 5,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
51
+ 5,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
52
+ 5,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
53
+ 5,F,10000,1.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
54
+ 5,F,10000,2,650,650,extravascular,10000,mg,0,mg/L,hr
55
+ 5,F,10000,4,1080,1080,extravascular,10000,mg,0,mg/L,hr
56
+ 5,F,10000,6,808,808,extravascular,10000,mg,0,mg/L,hr
57
+ 5,F,10000,8,571,571,extravascular,10000,mg,0,mg/L,hr
58
+ 5,F,10000,12,281,281,extravascular,10000,mg,0,mg/L,hr
59
+ 5,F,10000,16,138,138,extravascular,10000,mg,0,mg/L,hr
60
+ 5,F,10000,20,68,68,extravascular,10000,mg,0,mg/L,hr
61
+ 5,F,10000,24,33.5,33.5,extravascular,10000,mg,0,mg/L,hr
62
+ 6,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
63
+ 6,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
64
+ 6,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
65
+ 6,F,10000,1.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
66
+ 6,F,10000,2,1290,1290,extravascular,10000,mg,0,mg/L,hr
67
+ 6,F,10000,4,1170,1170,extravascular,10000,mg,0,mg/L,hr
68
+ 6,F,10000,6,589,589,extravascular,10000,mg,0,mg/L,hr
69
+ 6,F,10000,8,287,287,extravascular,10000,mg,0,mg/L,hr
70
+ 6,F,10000,12,67.5,67.5,extravascular,10000,mg,0,mg/L,hr
71
+ 6,F,10000,16,15.9,15.9,extravascular,10000,mg,0,mg/L,hr
72
+ 6,F,10000,20,3.74,3.74,extravascular,10000,mg,0,mg/L,hr
73
+ 6,F,10000,24,0.882,0.882,extravascular,10000,mg,0,mg/L,hr
74
+ 7,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
75
+ 7,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
76
+ 7,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
77
+ 7,F,10000,1.5,961,961,extravascular,10000,mg,0,mg/L,hr
78
+ 7,F,10000,2,1240,1240,extravascular,10000,mg,0,mg/L,hr
79
+ 7,F,10000,4,816,816,extravascular,10000,mg,0,mg/L,hr
80
+ 7,F,10000,6,443,443,extravascular,10000,mg,0,mg/L,hr
81
+ 7,F,10000,8,239,239,extravascular,10000,mg,0,mg/L,hr
82
+ 7,F,10000,12,69.4,69.4,extravascular,10000,mg,0,mg/L,hr
83
+ 7,F,10000,16,20.2,20.2,extravascular,10000,mg,0,mg/L,hr
84
+ 7,F,10000,20,5.86,5.86,extravascular,10000,mg,0,mg/L,hr
85
+ 7,F,10000,24,1.7,1.7,extravascular,10000,mg,0,mg/L,hr
86
+ 8,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
87
+ 8,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
88
+ 8,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
89
+ 8,F,10000,1.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
90
+ 8,F,10000,2,0,BLQ,extravascular,10000,mg,0,mg/L,hr
91
+ 8,F,10000,4,1400,1400,extravascular,10000,mg,0,mg/L,hr
92
+ 8,F,10000,6,777,777,extravascular,10000,mg,0,mg/L,hr
93
+ 8,F,10000,8,391,391,extravascular,10000,mg,0,mg/L,hr
94
+ 8,F,10000,12,97,97,extravascular,10000,mg,0,mg/L,hr
95
+ 8,F,10000,16,24,24,extravascular,10000,mg,0,mg/L,hr
96
+ 8,F,10000,20,5.96,5.96,extravascular,10000,mg,0,mg/L,hr
97
+ 8,F,10000,24,1.48,1.48,extravascular,10000,mg,0,mg/L,hr
98
+ 9,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
99
+ 9,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
100
+ 9,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
101
+ 9,F,10000,1.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
102
+ 9,F,10000,2,0,BLQ,extravascular,10000,mg,0,mg/L,hr
103
+ 9,F,10000,4,944,944,extravascular,10000,mg,0,mg/L,hr
104
+ 9,F,10000,6,654,654,extravascular,10000,mg,0,mg/L,hr
105
+ 9,F,10000,8,417,417,extravascular,10000,mg,0,mg/L,hr
106
+ 9,F,10000,12,168,168,extravascular,10000,mg,0,mg/L,hr
107
+ 9,F,10000,16,67.7,67.7,extravascular,10000,mg,0,mg/L,hr
108
+ 9,F,10000,20,27.3,27.3,extravascular,10000,mg,0,mg/L,hr
109
+ 9,F,10000,24,11,11,extravascular,10000,mg,0,mg/L,hr
110
+ 10,F,10000,0,0,BLQ,extravascular,10000,mg,0,mg/L,hr
111
+ 10,F,10000,0.5,0,BLQ,extravascular,10000,mg,0,mg/L,hr
112
+ 10,F,10000,1,0,BLQ,extravascular,10000,mg,0,mg/L,hr
113
+ 10,F,10000,1.5,657,657,extravascular,10000,mg,0,mg/L,hr
114
+ 10,F,10000,2,1130,1130,extravascular,10000,mg,0,mg/L,hr
115
+ 10,F,10000,4,1000,1000,extravascular,10000,mg,0,mg/L,hr
116
+ 10,F,10000,6,658,658,extravascular,10000,mg,0,mg/L,hr
117
+ 10,F,10000,8,425,425,extravascular,10000,mg,0,mg/L,hr
118
+ 10,F,10000,12,176,176,extravascular,10000,mg,0,mg/L,hr
119
+ 10,F,10000,16,73.2,73.2,extravascular,10000,mg,0,mg/L,hr
120
+ 10,F,10000,20,30.4,30.4,extravascular,10000,mg,0,mg/L,hr
121
+ 10,F,10000,24,12.6,12.6,extravascular,10000,mg,0,mg/L,hr
@@ -0,0 +1,34 @@
1
+ """
2
+ Copyright 2024 Aplos Analytics
3
+ All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
4
+ Property of Aplos Analytics, Utah, USA
5
+ """
6
+
7
+ from typing import Dict, Any
8
+ from aplos_nca_saas_sdk.utilities.environment_vars import EnvironmentVars
9
+ from aplos_nca_saas_sdk.nca_resources.nca_endpoints import NCAEndpoints
10
+
11
+
12
+ class IntegrationTestBase:
13
+ """
14
+ Integration Test Base Class
15
+ """
16
+
17
+ def __init__(self, name: str, index: int = 0):
18
+ self.name = name
19
+ self.index = index
20
+ self.env_vars: EnvironmentVars = EnvironmentVars()
21
+
22
+ if not self.env_vars.api_domain:
23
+ raise RuntimeError(
24
+ "APLOS_API_DOMAIN environment variable is not set. "
25
+ "This is required to run the tests"
26
+ )
27
+
28
+ self.endpoints: NCAEndpoints = NCAEndpoints(
29
+ aplos_saas_domain=self.env_vars.api_domain,
30
+ )
31
+
32
+ def test(self) -> Dict[str, Any] | None:
33
+ """Run the Test"""
34
+ raise RuntimeError("This should be implemented by the subclass.")
@@ -0,0 +1,62 @@
1
+ """
2
+ Copyright 2024 Aplos Analytics
3
+ All Rights Reserved. www.aplosanalytics.com LICENSED MATERIALS
4
+ Property of Aplos Analytics, Utah, USA
5
+ """
6
+
7
+ import os
8
+ from typing import List
9
+ from pathlib import Path
10
+ import importlib
11
+ import inspect
12
+ from aplos_nca_saas_sdk.integration_testing.integration_test_base import (
13
+ IntegrationTestBase,
14
+ )
15
+
16
+
17
+ class IntegrationTestFactory:
18
+ """
19
+ Integration Test Factory
20
+ Loads all the integration tests from the tests directory and registers them for execution
21
+ """
22
+
23
+ def __init__(self):
24
+ self.__test_classes: List[IntegrationTestBase] = []
25
+ self.__load_all_classes()
26
+
27
+ def __load_all_classes(self):
28
+ # find all files in the test directory that end in _test.py
29
+ test_directory = os.path.join(Path(__file__).parent, "tests")
30
+ potential_test_files = os.listdir(test_directory)
31
+ test_files = [
32
+ f
33
+ for f in potential_test_files
34
+ if f.endswith("_test.py") and f != "__init__.py"
35
+ ]
36
+
37
+ # load the class dynamically
38
+ for test_file in test_files:
39
+ module_name = (
40
+ f"aplos_nca_saas_sdk.integration_testing.tests.{test_file[:-3]}"
41
+ )
42
+ module = importlib.import_module(module_name)
43
+
44
+ # Iterate over all attributes in the module
45
+ for _, obj in inspect.getmembers(module, inspect.isclass):
46
+ # Check if the class inherits from the specified base class
47
+ if (
48
+ issubclass(obj, IntegrationTestBase)
49
+ and obj is not IntegrationTestBase
50
+ ):
51
+ # Instantiate the class and store it
52
+ self.register_test_class(obj())
53
+
54
+ @property
55
+ def test_classes(self) -> List[IntegrationTestBase]:
56
+ """Get the test classes"""
57
+ self.__test_classes.sort(key=lambda x: x.index)
58
+ return self.__test_classes
59
+
60
+ def register_test_class(self, test_class: IntegrationTestBase):
61
+ """Register a test class"""
62
+ self.__test_classes.append(test_class)