willisapi-client 1.9.3__tar.gz → 1.9.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/PKG-INFO +17 -4
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/README.md +16 -3
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/__init__.py +2 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/__version__.py +1 -1
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/api.py +1 -1
- willisapi_client-1.9.4/willisapi_client/services/metadata/__init__.py +4 -0
- willisapi_client-1.9.4/willisapi_client/services/metadata/upload.py +194 -0
- willisapi_client-1.9.4/willisapi_client/services/metadata/utils.py +746 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/willisapi_client.py +3 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/PKG-INFO +17 -4
- willisapi_client-1.9.3/willisapi_client/services/metadata/__init__.py +0 -4
- willisapi_client-1.9.3/willisapi_client/services/metadata/upload.py +0 -91
- willisapi_client-1.9.3/willisapi_client/services/metadata/utils.py +0 -350
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/LICENSE +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/MANIFEST.in +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/RELEASE.md +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/requirements.txt +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/setup.cfg +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/setup.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/tests/test_diarize.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/tests/test_diarize_call_remaining.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/logging_setup.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/__init__.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/diarize/__init__.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/diarize/diarize_utils.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/diarize/willisdiarize.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/diarize/willisdiarize_call_remaining.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/exceptions.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/metadata/language_choices.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/timer.py +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/SOURCES.txt +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/dependency_links.txt +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/not-zip-safe +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/requires.txt +0 -0
- {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: willisapi_client
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.4
|
|
4
4
|
Summary: A Python client for willisapi
|
|
5
5
|
Home-page: https://github.com/bklynhlth/willsiapi_client
|
|
6
6
|
Author: bklynhlth
|
|
@@ -95,11 +95,10 @@ summary = willisapi.upload(key, 'data.csv', force_uploade=True)
|
|
|
95
95
|
|
|
96
96
|
Before uploading, the client validates your CSV for:
|
|
97
97
|
|
|
98
|
-
- **Required Columns**: Must include study_id, site_id,
|
|
99
|
-
- **Optional Columns**: age, sex, race, language.
|
|
98
|
+
- **Required Columns**: Must include study_id, site_id, participant_id, visit_name, visit_order, coa_name, coa_item_number, coa_item_value, file_path, time_collected.
|
|
99
|
+
- **Optional Columns**: rater_id, age, sex, race, language.
|
|
100
100
|
- **Valid COA Name**: Only these values are allowed: MADRS, YMRS, PHQ-9, GAD-7.
|
|
101
101
|
- **Valid Audio File**: The file path in each row must exist and be accessible.
|
|
102
|
-
- **Valid Email**: rater_email must be a valid email.
|
|
103
102
|
- **Valid Data Types**: visit_order, age, coa_item_number, and coa_item_value must be numeric.
|
|
104
103
|
- **Language**: If present, must be in the allowed language choices.
|
|
105
104
|
|
|
@@ -107,6 +106,20 @@ If any validation fails, errors are collected and returned for review before upl
|
|
|
107
106
|
|
|
108
107
|
---
|
|
109
108
|
|
|
109
|
+
**Processed Data Upload**
|
|
110
|
+
|
|
111
|
+
To upload a processed data CSV file:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
summary = willisapi.processed_upload(key, '/path/to/processed-data.csv')
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
How to Call the Function
|
|
118
|
+
- key: Your PAT token.
|
|
119
|
+
- processed-data.csv: Path to your processed data CSV file.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
110
123
|
For more information on how to organize the `data.csv`, visit the [Github Wiki](http://www.github.com/bklynhlth/willisapi_client/wiki).
|
|
111
124
|
|
|
112
125
|
|
|
@@ -83,11 +83,10 @@ summary = willisapi.upload(key, 'data.csv', force_uploade=True)
|
|
|
83
83
|
|
|
84
84
|
Before uploading, the client validates your CSV for:
|
|
85
85
|
|
|
86
|
-
- **Required Columns**: Must include study_id, site_id,
|
|
87
|
-
- **Optional Columns**: age, sex, race, language.
|
|
86
|
+
- **Required Columns**: Must include study_id, site_id, participant_id, visit_name, visit_order, coa_name, coa_item_number, coa_item_value, file_path, time_collected.
|
|
87
|
+
- **Optional Columns**: rater_id, age, sex, race, language.
|
|
88
88
|
- **Valid COA Name**: Only these values are allowed: MADRS, YMRS, PHQ-9, GAD-7.
|
|
89
89
|
- **Valid Audio File**: The file path in each row must exist and be accessible.
|
|
90
|
-
- **Valid Email**: rater_email must be a valid email.
|
|
91
90
|
- **Valid Data Types**: visit_order, age, coa_item_number, and coa_item_value must be numeric.
|
|
92
91
|
- **Language**: If present, must be in the allowed language choices.
|
|
93
92
|
|
|
@@ -95,6 +94,20 @@ If any validation fails, errors are collected and returned for review before upl
|
|
|
95
94
|
|
|
96
95
|
---
|
|
97
96
|
|
|
97
|
+
**Processed Data Upload**
|
|
98
|
+
|
|
99
|
+
To upload a processed data CSV file:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
summary = willisapi.processed_upload(key, '/path/to/processed-data.csv')
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
How to Call the Function
|
|
106
|
+
- key: Your PAT token.
|
|
107
|
+
- processed-data.csv: Path to your processed data CSV file.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
98
111
|
For more information on how to organize the `data.csv`, visit the [Github Wiki](http://www.github.com/bklynhlth/willisapi_client/wiki).
|
|
99
112
|
|
|
100
113
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Version details for willisapi_client"""
|
|
2
2
|
|
|
3
3
|
__client__ = "willisapi_client"
|
|
4
|
-
__latestVersion__ = "1.9.
|
|
4
|
+
__latestVersion__ = "1.9.4"
|
|
5
5
|
__url__ = "https://github.com/bklynhlth/willsiapi_client"
|
|
6
6
|
__short_description__ = "A Python client for willisapi"
|
|
7
7
|
__content_type__ = "text/markdown"
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from tqdm import tqdm
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import requests
|
|
5
|
+
import mimetypes
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from willisapi_client.timer import measure
|
|
9
|
+
from willisapi_client.willisapi_client import WillisapiClient
|
|
10
|
+
from willisapi_client.logging_setup import logger as logger
|
|
11
|
+
from willisapi_client.services.metadata.utils import (
|
|
12
|
+
MetadataValidation,
|
|
13
|
+
ProcessedMetadataValidation,
|
|
14
|
+
UploadUtils,
|
|
15
|
+
find_files_with_pattern,
|
|
16
|
+
get_last_n_directories,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@measure
|
|
21
|
+
def upload(api_key: str, csv_path: str, **kwargs):
|
|
22
|
+
|
|
23
|
+
force_upload = kwargs.get("force_upload", False)
|
|
24
|
+
csv = MetadataValidation(csv_path=csv_path, force_upload=force_upload)
|
|
25
|
+
if csv.load_and_validate():
|
|
26
|
+
logger.info(f'{datetime.now().strftime("%H:%M:%S")}: csv check passed')
|
|
27
|
+
csv.create_final_csv()
|
|
28
|
+
|
|
29
|
+
wc = WillisapiClient(env=kwargs.get("env"))
|
|
30
|
+
url = wc.get_upload_url()
|
|
31
|
+
headers = wc.get_headers()
|
|
32
|
+
headers["Authorization"] = f"token {api_key}"
|
|
33
|
+
logger.info(f'{datetime.now().strftime("%H:%M:%S")}: beginning upload')
|
|
34
|
+
|
|
35
|
+
results = []
|
|
36
|
+
for index, row in tqdm(
|
|
37
|
+
csv.transformed_df.iterrows(), total=csv.transformed_df.shape[0]
|
|
38
|
+
):
|
|
39
|
+
u = UploadUtils(row)
|
|
40
|
+
valid, err = u.validate_row()
|
|
41
|
+
result_row = row.to_dict()
|
|
42
|
+
if valid:
|
|
43
|
+
payload = u.generate_payload()
|
|
44
|
+
res = u.post(api_key, url, headers, payload)
|
|
45
|
+
if res.get("upload_status") == "Success":
|
|
46
|
+
result_row["upload_status"] = "Success"
|
|
47
|
+
result_row["error"] = None
|
|
48
|
+
|
|
49
|
+
# Handle S3 upload if presigned URL is provided
|
|
50
|
+
presigned = res.get("response", {}).get("presigned")
|
|
51
|
+
if presigned:
|
|
52
|
+
try:
|
|
53
|
+
content_type, _ = mimetypes.guess_type(
|
|
54
|
+
payload.get("filename")
|
|
55
|
+
)
|
|
56
|
+
if not content_type:
|
|
57
|
+
content_type = "application/octet-stream"
|
|
58
|
+
with open(row.file_path, "rb") as f:
|
|
59
|
+
response = requests.put(
|
|
60
|
+
presigned,
|
|
61
|
+
data=f,
|
|
62
|
+
headers={
|
|
63
|
+
"x-amz-checksum-sha256": payload.get(
|
|
64
|
+
"checksum"
|
|
65
|
+
),
|
|
66
|
+
"x-amz-sdk-checksum-algorithm": "SHA256",
|
|
67
|
+
"Content-Type": content_type,
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
if response.status_code == 200:
|
|
71
|
+
result_row["upload_status"] = "Success"
|
|
72
|
+
else:
|
|
73
|
+
result_row["upload_status"] = "Failed"
|
|
74
|
+
result_row["error"] = (
|
|
75
|
+
f"S3 upload failed with status code {response.status_code}"
|
|
76
|
+
)
|
|
77
|
+
except Exception as ex:
|
|
78
|
+
result_row["upload_status"] = "Failed"
|
|
79
|
+
result_row["error"] = str(ex)
|
|
80
|
+
else:
|
|
81
|
+
result_row["upload_status"] = "Failed"
|
|
82
|
+
result_row["error"] = (
|
|
83
|
+
"Collect recording upload URL not received"
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
result_row["upload_status"] = "Failed"
|
|
87
|
+
result_row["error"] = res.get("error")
|
|
88
|
+
else:
|
|
89
|
+
result_row["upload_status"] = "Failed"
|
|
90
|
+
result_row["error"] = f"{err}"
|
|
91
|
+
results.append(result_row)
|
|
92
|
+
|
|
93
|
+
results_df = pd.DataFrame(results)
|
|
94
|
+
return results_df
|
|
95
|
+
else:
|
|
96
|
+
logger.error(f'{datetime.now().strftime("%H:%M:%S")}: csv check failed')
|
|
97
|
+
logger.error(csv.errors)
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@measure
|
|
102
|
+
def processed_upload(api_key: str, csv_path: str, output_path: str, **kwargs):
|
|
103
|
+
|
|
104
|
+
force_upload = kwargs.get("force_upload", False)
|
|
105
|
+
csv = ProcessedMetadataValidation(csv_path=csv_path, force_upload=force_upload)
|
|
106
|
+
if csv.load_and_validate():
|
|
107
|
+
logger.info(f'{datetime.now().strftime("%H:%M:%S")}: csv check passed')
|
|
108
|
+
csv.create_final_csv()
|
|
109
|
+
|
|
110
|
+
wc = WillisapiClient(env=kwargs.get("env"))
|
|
111
|
+
url = wc.get_processed_upload_url()
|
|
112
|
+
headers = wc.get_headers()
|
|
113
|
+
headers["Authorization"] = f"token {api_key}"
|
|
114
|
+
logger.info(f'{datetime.now().strftime("%H:%M:%S")}: beginning upload')
|
|
115
|
+
|
|
116
|
+
results = []
|
|
117
|
+
for index, row in tqdm(
|
|
118
|
+
csv.transformed_df.iterrows(), total=csv.transformed_df.shape[0]
|
|
119
|
+
):
|
|
120
|
+
u = UploadUtils(row)
|
|
121
|
+
valid, err = u.validate_processed_data_row()
|
|
122
|
+
result_row = row.to_dict()
|
|
123
|
+
if valid:
|
|
124
|
+
filename = os.path.basename(row.recording).split(".")[0]
|
|
125
|
+
files = []
|
|
126
|
+
for file in find_files_with_pattern(output_path, filename):
|
|
127
|
+
key, error = get_last_n_directories(file, n=2)
|
|
128
|
+
if error:
|
|
129
|
+
continue
|
|
130
|
+
checksum = u.calculate_file_checksum(file)
|
|
131
|
+
files.append(
|
|
132
|
+
{
|
|
133
|
+
"index": index,
|
|
134
|
+
"recording": file,
|
|
135
|
+
"key": key,
|
|
136
|
+
"checksum": checksum,
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
payload = u.generate_processed_payload(files)
|
|
140
|
+
res = u.post(api_key, url, headers, payload)
|
|
141
|
+
if res.get("upload_status") == "Success":
|
|
142
|
+
result_row["upload_status"] = "Success"
|
|
143
|
+
result_row["error"] = None
|
|
144
|
+
|
|
145
|
+
# Handle S3 upload if presigned URL is provided
|
|
146
|
+
for file_presigned in res.get("response", []):
|
|
147
|
+
presigned = file_presigned.get("presigned")
|
|
148
|
+
checksum = file_presigned.get("checksum")
|
|
149
|
+
recording = file_presigned.get("recording")
|
|
150
|
+
index = file_presigned.get("index")
|
|
151
|
+
try:
|
|
152
|
+
content_type, _ = mimetypes.guess_type(recording)
|
|
153
|
+
if not content_type:
|
|
154
|
+
content_type = "text/csv"
|
|
155
|
+
with open(recording, "rb") as f:
|
|
156
|
+
response = requests.put(
|
|
157
|
+
presigned,
|
|
158
|
+
data=f,
|
|
159
|
+
headers={
|
|
160
|
+
"x-amz-checksum-sha256": checksum,
|
|
161
|
+
"x-amz-sdk-checksum-algorithm": "SHA256",
|
|
162
|
+
"Content-Type": content_type,
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
if response.status_code == 200:
|
|
166
|
+
result_row["upload_status"] = "Success"
|
|
167
|
+
else:
|
|
168
|
+
result_row["upload_status"] = "Failed"
|
|
169
|
+
if result_row["error"] is None:
|
|
170
|
+
result_row["error"] = ""
|
|
171
|
+
result_row["error"] = (
|
|
172
|
+
result_row["error"]
|
|
173
|
+
+ "\n"
|
|
174
|
+
+ f"S3 upload failed with status code {response.status_code} for file {recording}"
|
|
175
|
+
)
|
|
176
|
+
except Exception as ex:
|
|
177
|
+
result_row["upload_status"] = "Failed"
|
|
178
|
+
if result_row["error"] is None:
|
|
179
|
+
result_row["error"] = ""
|
|
180
|
+
result_row["error"] = result_row["error"] + "\n" + str(ex)
|
|
181
|
+
else:
|
|
182
|
+
result_row["upload_status"] = "Failed"
|
|
183
|
+
result_row["error"] = res.get("error")
|
|
184
|
+
else:
|
|
185
|
+
result_row["upload_status"] = "Failed"
|
|
186
|
+
result_row["error"] = f"{err}"
|
|
187
|
+
results.append(result_row)
|
|
188
|
+
|
|
189
|
+
results_df = pd.DataFrame(results)
|
|
190
|
+
return results_df
|
|
191
|
+
else:
|
|
192
|
+
logger.error(f'{datetime.now().strftime("%H:%M:%S")}: csv check failed')
|
|
193
|
+
logger.error(csv.errors)
|
|
194
|
+
return None
|