willisapi-client 1.9.8__tar.gz → 2.0__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.8 → willisapi_client-2.0}/PKG-INFO +35 -2
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/__version__.py +1 -1
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/metadata/upload.py +15 -6
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/metadata/utils.py +45 -6
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client.egg-info/PKG-INFO +36 -3
- {willisapi_client-1.9.8 → willisapi_client-2.0}/LICENSE +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/MANIFEST.in +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/README.md +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/RELEASE.md +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/requirements.txt +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/setup.cfg +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/setup.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/tests/test_diarize.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/tests/test_diarize_call_remaining.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/__init__.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/logging_setup.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/__init__.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/api.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/diarize/__init__.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/diarize/diarize_utils.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/diarize/willisdiarize.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/diarize/willisdiarize_call_remaining.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/exceptions.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/metadata/__init__.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/metadata/archive.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/metadata/language_choices.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/timer.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/willisapi_client.py +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client.egg-info/SOURCES.txt +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client.egg-info/dependency_links.txt +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client.egg-info/not-zip-safe +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client.egg-info/requires.txt +0 -0
- {willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: willisapi_client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0
|
|
4
4
|
Summary: A Python client for willisapi
|
|
5
5
|
Home-page: https://github.com/bklynhlth/willsiapi_client
|
|
6
6
|
Author: bklynhlth
|
|
@@ -9,6 +9,39 @@ License: Apache
|
|
|
9
9
|
Requires-Python: >=3.9
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
|
+
Requires-Dist: aiohttp==3.9.0
|
|
13
|
+
Requires-Dist: aiosignal==1.3.1
|
|
14
|
+
Requires-Dist: async-timeout==4.0.2
|
|
15
|
+
Requires-Dist: asyncio==3.4.3
|
|
16
|
+
Requires-Dist: attrs==23.1.0
|
|
17
|
+
Requires-Dist: certifi==2023.7.22
|
|
18
|
+
Requires-Dist: charset-normalizer==3.2.0
|
|
19
|
+
Requires-Dist: frozenlist==1.4.0
|
|
20
|
+
Requires-Dist: idna==3.4
|
|
21
|
+
Requires-Dist: multidict==6.0.4
|
|
22
|
+
Requires-Dist: nest-asyncio==1.5.7
|
|
23
|
+
Requires-Dist: numpy==1.25.2
|
|
24
|
+
Requires-Dist: pandas==2.0.3
|
|
25
|
+
Requires-Dist: pytest==7.4.0
|
|
26
|
+
Requires-Dist: pytest-cov==4.1.0
|
|
27
|
+
Requires-Dist: python-dateutil==2.8.2
|
|
28
|
+
Requires-Dist: pytz==2023.3
|
|
29
|
+
Requires-Dist: requests==2.31.0
|
|
30
|
+
Requires-Dist: six==1.16.0
|
|
31
|
+
Requires-Dist: tqdm==4.66.1
|
|
32
|
+
Requires-Dist: tzdata==2023.3
|
|
33
|
+
Requires-Dist: urllib3==2.0.7
|
|
34
|
+
Requires-Dist: yarl==1.9.2
|
|
35
|
+
Dynamic: author
|
|
36
|
+
Dynamic: author-email
|
|
37
|
+
Dynamic: description
|
|
38
|
+
Dynamic: description-content-type
|
|
39
|
+
Dynamic: home-page
|
|
40
|
+
Dynamic: license
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
Dynamic: requires-dist
|
|
43
|
+
Dynamic: requires-python
|
|
44
|
+
Dynamic: summary
|
|
12
45
|
|
|
13
46
|
<!-- Output copied to clipboard! -->
|
|
14
47
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Version details for willisapi_client"""
|
|
2
2
|
|
|
3
3
|
__client__ = "willisapi_client"
|
|
4
|
-
__latestVersion__ = "
|
|
4
|
+
__latestVersion__ = "2.0"
|
|
5
5
|
__url__ = "https://github.com/bklynhlth/willsiapi_client"
|
|
6
6
|
__short_description__ = "A Python client for willisapi"
|
|
7
7
|
__content_type__ = "text/markdown"
|
{willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/metadata/upload.py
RENAMED
|
@@ -7,9 +7,16 @@ import os
|
|
|
7
7
|
import time
|
|
8
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
10
|
+
# The server signs presigned URLs with Content-Types guessed from Python's
|
|
11
|
+
# built-in mimetypes table (on Linux), so the client must produce identical
|
|
12
|
+
# guesses. A private MimeTypes instance holds only that built-in table —
|
|
13
|
+
# the module-level mimetypes.guess_type() on Windows also reads the registry
|
|
14
|
+
# (e.g. .csv -> application/vnd.ms-excel, .wav -> audio/wav), which makes the
|
|
15
|
+
# PUT's Content-Type differ from the signed one and S3 reject the upload with
|
|
16
|
+
# SignatureDoesNotMatch. Also register extensions the built-in table lacks,
|
|
17
|
+
# mirroring the server.
|
|
18
|
+
_MIME_TYPES = mimetypes.MimeTypes()
|
|
19
|
+
_MIME_TYPES.add_type("application/vnd.apache.parquet", ".parquet")
|
|
13
20
|
|
|
14
21
|
from willisapi_client.timer import measure
|
|
15
22
|
from willisapi_client.willisapi_client import WillisapiClient
|
|
@@ -41,9 +48,11 @@ def _put_file_to_s3(file_presigned: dict):
|
|
|
41
48
|
checksum = file_presigned.get("checksum")
|
|
42
49
|
recording = file_presigned.get("recording")
|
|
43
50
|
try:
|
|
44
|
-
content_type, _ =
|
|
51
|
+
content_type, _ = _MIME_TYPES.guess_type(recording)
|
|
45
52
|
if not content_type:
|
|
46
|
-
|
|
53
|
+
# Mirror the server's fallback — it signs the presigned URL with
|
|
54
|
+
# this Content-Type when the extension is unknown.
|
|
55
|
+
content_type = "application/octet-stream"
|
|
47
56
|
with open(recording, "rb") as f:
|
|
48
57
|
session = build_retry_session()
|
|
49
58
|
response = session.put(
|
|
@@ -108,7 +117,7 @@ def upload(api_key: str, csv_path: str, **kwargs):
|
|
|
108
117
|
presigned = res.get("response", {}).get("presigned")
|
|
109
118
|
if presigned:
|
|
110
119
|
try:
|
|
111
|
-
content_type, _ =
|
|
120
|
+
content_type, _ = _MIME_TYPES.guess_type(
|
|
112
121
|
payload.get("filename")
|
|
113
122
|
)
|
|
114
123
|
if not content_type:
|
|
@@ -3,6 +3,7 @@ import numpy as np
|
|
|
3
3
|
from typing import List, Dict, Any, Tuple
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
+
import posixpath
|
|
6
7
|
import hashlib
|
|
7
8
|
import base64
|
|
8
9
|
import requests
|
|
@@ -472,15 +473,48 @@ class UploadUtils:
|
|
|
472
473
|
except Exception as e:
|
|
473
474
|
raise RuntimeError(f"Failed to calculate checksum: {e}")
|
|
474
475
|
|
|
476
|
+
def optional_value(self, column: str):
|
|
477
|
+
"""Return a column's value only when the row actually supplies one.
|
|
478
|
+
|
|
479
|
+
Optional columns may be missing from the CSV entirely, or present but
|
|
480
|
+
empty on a given row -- pandas surfaces both as an absent attribute or
|
|
481
|
+
NaN. Passing that straight through would post the string "nan" to the
|
|
482
|
+
API and overwrite good data, so it is dropped instead. Callers omit
|
|
483
|
+
the key altogether, which the API treats as "leave this field alone".
|
|
484
|
+
"""
|
|
485
|
+
value = getattr(self.row, column, None)
|
|
486
|
+
if value is None or pd.isna(value):
|
|
487
|
+
return None
|
|
488
|
+
text = str(value).strip()
|
|
489
|
+
return text or None
|
|
490
|
+
|
|
491
|
+
def demographic_fields(self) -> Dict[str, Any]:
|
|
492
|
+
"""Optional demographics to send, omitting any the row leaves blank."""
|
|
493
|
+
supplied = {}
|
|
494
|
+
for column in ("sex", "race"):
|
|
495
|
+
value = self.optional_value(column)
|
|
496
|
+
if value is not None:
|
|
497
|
+
supplied[column] = value
|
|
498
|
+
|
|
499
|
+
age = self.optional_value("age")
|
|
500
|
+
if age is not None:
|
|
501
|
+
try:
|
|
502
|
+
# Via float first: pandas widens an int column to float as soon
|
|
503
|
+
# as any row is blank, so a whole-number age arrives as "42.0".
|
|
504
|
+
supplied["age"] = int(float(age))
|
|
505
|
+
except (TypeError, ValueError):
|
|
506
|
+
# MetadataValidation already reports a non-numeric age; dropping
|
|
507
|
+
# it here keeps a bad cell from failing the upload as well.
|
|
508
|
+
pass
|
|
509
|
+
|
|
510
|
+
return supplied
|
|
511
|
+
|
|
475
512
|
def generate_payload(self) -> Dict[str, Any]:
|
|
476
513
|
payload = {
|
|
477
514
|
"study_id": self.row.study_id,
|
|
478
515
|
"site_id": self.row.site_id,
|
|
479
516
|
"rater_id": self.row.rater_id,
|
|
480
517
|
"participant_id": self.row.participant_id,
|
|
481
|
-
"age": self.row.age,
|
|
482
|
-
"sex": self.row.sex,
|
|
483
|
-
"race": self.row.race,
|
|
484
518
|
"language": self.row.language,
|
|
485
519
|
"visit_name": self.row.visit_name,
|
|
486
520
|
"visit_order": int(self.row.visit_order),
|
|
@@ -493,6 +527,7 @@ class UploadUtils:
|
|
|
493
527
|
"is_last_recording": self.row.is_last_recording,
|
|
494
528
|
# "time_collected": parser.parse(self.row.time_collected).isoformat(),
|
|
495
529
|
}
|
|
530
|
+
payload.update(self.demographic_fields())
|
|
496
531
|
return payload
|
|
497
532
|
|
|
498
533
|
def generate_processed_payload(
|
|
@@ -518,6 +553,7 @@ class UploadUtils:
|
|
|
518
553
|
site_country = getattr(self.row, "site_country", None)
|
|
519
554
|
if site_country:
|
|
520
555
|
payload["site_country"] = site_country
|
|
556
|
+
payload.update(self.demographic_fields())
|
|
521
557
|
return payload
|
|
522
558
|
|
|
523
559
|
def post(
|
|
@@ -757,7 +793,8 @@ class ProcessedMetadataValidation:
|
|
|
757
793
|
orig_bucket, orig_key = _get_bucket_n_key_path_from_s3url(
|
|
758
794
|
grp.iloc[0]["recording"]
|
|
759
795
|
)
|
|
760
|
-
|
|
796
|
+
# S3 keys always use "/" — os.path.join would insert "\" on Windows.
|
|
797
|
+
merged_key = posixpath.join(posixpath.dirname(orig_key), merged_name)
|
|
761
798
|
|
|
762
799
|
# Create new URI (no upload, logical merge only)
|
|
763
800
|
new_uri = f"s3://{orig_bucket}/{merged_key}"
|
|
@@ -810,9 +847,11 @@ def get_last_n_directories(filepath, n=3):
|
|
|
810
847
|
result = None
|
|
811
848
|
error = None
|
|
812
849
|
try:
|
|
813
|
-
|
|
850
|
+
# Normalize Windows backslashes so the result is always a
|
|
851
|
+
# forward-slash S3 key regardless of the local OS.
|
|
852
|
+
parts = filepath.replace("\\", "/").split("/")
|
|
814
853
|
last_parts = parts[-(n + 1) :]
|
|
815
|
-
result =
|
|
854
|
+
result = "/".join(last_parts)
|
|
816
855
|
except Exception as e:
|
|
817
856
|
error = "Could not extract container name and container version from file path"
|
|
818
857
|
return result, error
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name:
|
|
3
|
-
Version:
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: willisapi_client
|
|
3
|
+
Version: 2.0
|
|
4
4
|
Summary: A Python client for willisapi
|
|
5
5
|
Home-page: https://github.com/bklynhlth/willsiapi_client
|
|
6
6
|
Author: bklynhlth
|
|
@@ -9,6 +9,39 @@ License: Apache
|
|
|
9
9
|
Requires-Python: >=3.9
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
|
+
Requires-Dist: aiohttp==3.9.0
|
|
13
|
+
Requires-Dist: aiosignal==1.3.1
|
|
14
|
+
Requires-Dist: async-timeout==4.0.2
|
|
15
|
+
Requires-Dist: asyncio==3.4.3
|
|
16
|
+
Requires-Dist: attrs==23.1.0
|
|
17
|
+
Requires-Dist: certifi==2023.7.22
|
|
18
|
+
Requires-Dist: charset-normalizer==3.2.0
|
|
19
|
+
Requires-Dist: frozenlist==1.4.0
|
|
20
|
+
Requires-Dist: idna==3.4
|
|
21
|
+
Requires-Dist: multidict==6.0.4
|
|
22
|
+
Requires-Dist: nest-asyncio==1.5.7
|
|
23
|
+
Requires-Dist: numpy==1.25.2
|
|
24
|
+
Requires-Dist: pandas==2.0.3
|
|
25
|
+
Requires-Dist: pytest==7.4.0
|
|
26
|
+
Requires-Dist: pytest-cov==4.1.0
|
|
27
|
+
Requires-Dist: python-dateutil==2.8.2
|
|
28
|
+
Requires-Dist: pytz==2023.3
|
|
29
|
+
Requires-Dist: requests==2.31.0
|
|
30
|
+
Requires-Dist: six==1.16.0
|
|
31
|
+
Requires-Dist: tqdm==4.66.1
|
|
32
|
+
Requires-Dist: tzdata==2023.3
|
|
33
|
+
Requires-Dist: urllib3==2.0.7
|
|
34
|
+
Requires-Dist: yarl==1.9.2
|
|
35
|
+
Dynamic: author
|
|
36
|
+
Dynamic: author-email
|
|
37
|
+
Dynamic: description
|
|
38
|
+
Dynamic: description-content-type
|
|
39
|
+
Dynamic: home-page
|
|
40
|
+
Dynamic: license
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
Dynamic: requires-dist
|
|
43
|
+
Dynamic: requires-python
|
|
44
|
+
Dynamic: summary
|
|
12
45
|
|
|
13
46
|
<!-- Output copied to clipboard! -->
|
|
14
47
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/diarize/__init__.py
RENAMED
|
File without changes
|
{willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/diarize/diarize_utils.py
RENAMED
|
File without changes
|
{willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/diarize/willisdiarize.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/metadata/__init__.py
RENAMED
|
File without changes
|
{willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client/services/metadata/archive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{willisapi_client-1.9.8 → willisapi_client-2.0}/willisapi_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|