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.
- aplos_nca_saas_sdk/__init__.py +0 -0
- aplos_nca_saas_sdk/aws_resources/aws_cognito.py +188 -0
- aplos_nca_saas_sdk/aws_resources/aws_s3_presigned_payload.py +49 -0
- aplos_nca_saas_sdk/aws_resources/aws_s3_presigned_upload.py +116 -0
- aplos_nca_saas_sdk/files/analysis_files/single_ev/configuration_single_ev.json +51 -0
- aplos_nca_saas_sdk/files/analysis_files/single_ev/meta_data.json +17 -0
- aplos_nca_saas_sdk/files/analysis_files/single_ev/single_ev.csv +121 -0
- aplos_nca_saas_sdk/integration_testing/integration_test_base.py +34 -0
- aplos_nca_saas_sdk/integration_testing/integration_test_factory.py +62 -0
- aplos_nca_saas_sdk/integration_testing/integration_test_suite.py +84 -0
- aplos_nca_saas_sdk/integration_testing/main.py +28 -0
- aplos_nca_saas_sdk/integration_testing/readme.md +18 -0
- aplos_nca_saas_sdk/integration_testing/tests/app_configuration_test.py +30 -0
- aplos_nca_saas_sdk/integration_testing/tests/app_execution_test.py +5 -0
- aplos_nca_saas_sdk/integration_testing/tests/app_login_test.py +32 -0
- aplos_nca_saas_sdk/integration_testing/tests/app_validation_test.py +5 -0
- aplos_nca_saas_sdk/nca_resources/nca_app_configuration.py +69 -0
- aplos_nca_saas_sdk/nca_resources/nca_endpoints.py +54 -0
- aplos_nca_saas_sdk/nca_resources/nca_executions.py +378 -0
- aplos_nca_saas_sdk/nca_resources/nca_login.py +104 -0
- aplos_nca_saas_sdk/utilities/commandline_args.py +332 -0
- aplos_nca_saas_sdk/utilities/environment_services.py +81 -0
- aplos_nca_saas_sdk/utilities/environment_vars.py +23 -0
- aplos_nca_saas_sdk/utilities/http_utility.py +30 -0
- aplos_nca_saas_sdk/version.py +4 -0
- aplos_nca_saas_sdk-1.0.0.dist-info/METADATA +195 -0
- aplos_nca_saas_sdk-1.0.0.dist-info/RECORD +28 -0
- 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)
|