qmenta-client 2.1.dev1513__tar.gz → 2.1.dev1515__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.
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/PKG-INFO +2 -2
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/pyproject.toml +2 -2
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/src/qmenta/client/Project.py +58 -144
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/src/qmenta/__init__.py +0 -0
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/src/qmenta/client/Account.py +0 -0
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/src/qmenta/client/File.py +0 -0
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/src/qmenta/client/Subject.py +0 -0
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/src/qmenta/client/__init__.py +0 -0
- {qmenta_client-2.1.dev1513 → qmenta_client-2.1.dev1515}/src/qmenta/client/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: qmenta-client
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.dev1515
|
|
4
4
|
Summary: Python client lib to interact with the QMENTA platform.
|
|
5
5
|
Author: QMENTA
|
|
6
6
|
Author-email: dev@qmenta.com
|
|
@@ -14,5 +14,5 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Requires-Dist: future (>=0.18.2,<0.19.0)
|
|
16
16
|
Requires-Dist: pytest-cov (>=6.1.1,<7.0.0)
|
|
17
|
-
Requires-Dist: qmenta-core (
|
|
17
|
+
Requires-Dist: qmenta-core (==4.1)
|
|
18
18
|
Project-URL: Homepage, https://www.qmenta.com/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "qmenta-client"
|
|
3
|
-
version = "2.1.
|
|
3
|
+
version = "2.1.dev1515"
|
|
4
4
|
description = "Python client lib to interact with the QMENTA platform."
|
|
5
5
|
authors = ["QMENTA <dev@qmenta.com>"]
|
|
6
6
|
homepage = "https://www.qmenta.com/"
|
|
@@ -15,7 +15,7 @@ packages = [
|
|
|
15
15
|
[tool.poetry.dependencies]
|
|
16
16
|
python = "^3.10"
|
|
17
17
|
future = "^0.18.2"
|
|
18
|
-
qmenta-core = "
|
|
18
|
+
qmenta-core = "4.1"
|
|
19
19
|
pytest-cov = "^6.1.1"
|
|
20
20
|
|
|
21
21
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -9,8 +9,10 @@ import sys
|
|
|
9
9
|
import time
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, Union
|
|
12
13
|
from qmenta.core import errors
|
|
13
14
|
from qmenta.core import platform
|
|
15
|
+
from qmenta.core.upload.single import SingleUpload, FileInfo, UploadStatus
|
|
14
16
|
|
|
15
17
|
from qmenta.client import Account
|
|
16
18
|
|
|
@@ -262,12 +264,12 @@ class Project:
|
|
|
262
264
|
description="",
|
|
263
265
|
result=False,
|
|
264
266
|
name="",
|
|
265
|
-
input_data_type="qmenta_medical_image_data:3.
|
|
267
|
+
input_data_type="qmenta_medical_image_data:3.11.3",
|
|
266
268
|
add_to_container_id=0,
|
|
267
|
-
chunk_size=2**
|
|
268
|
-
max_retries=
|
|
269
|
+
chunk_size=2**24, # Optimized for GCS Bucket Storage
|
|
270
|
+
max_retries=5,
|
|
269
271
|
split_data=False,
|
|
270
|
-
mock_response=
|
|
272
|
+
mock_response=None,
|
|
271
273
|
):
|
|
272
274
|
"""
|
|
273
275
|
Upload a ZIP file to the platform.
|
|
@@ -295,136 +297,55 @@ class Project:
|
|
|
295
297
|
chunk_size : int
|
|
296
298
|
Size in kB of each chunk. Should be expressed as
|
|
297
299
|
a power of 2: 2**x. Default value of x is 9 (chunk_size = 512 kB)
|
|
300
|
+
max_retries: int
|
|
301
|
+
Maximum number of retries when uploading a file before raising
|
|
302
|
+
an error. Default value: 5
|
|
298
303
|
split_data : bool
|
|
299
304
|
If True, the platform will try to split the uploaded file into
|
|
300
305
|
different sessions. It will be ignored when the ssid or a
|
|
301
306
|
add_to_container_id are given.
|
|
307
|
+
mock_response: None
|
|
308
|
+
ONLY USED IN UNITTESTING
|
|
302
309
|
|
|
303
310
|
Returns
|
|
304
311
|
-------
|
|
305
312
|
bool
|
|
306
313
|
True if correctly uploaded, False otherwise.
|
|
307
314
|
"""
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
chunk_size *= 1024
|
|
312
|
-
|
|
313
|
-
name = name or os.path.split(file_path)[1]
|
|
314
|
-
|
|
315
|
-
total_bytes = os.path.getsize(file_path)
|
|
316
|
-
|
|
317
|
-
split_data = self.__assert_split_data(
|
|
318
|
-
split_data, ssid, add_to_container_id
|
|
315
|
+
input_data_type = (
|
|
316
|
+
"qmenta_upload_offline_analysis:1.0" if result else input_data_type
|
|
319
317
|
)
|
|
320
318
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
319
|
+
single_upload = SingleUpload(
|
|
320
|
+
self._account.auth,
|
|
321
|
+
file_path,
|
|
322
|
+
file_info=FileInfo(
|
|
323
|
+
project_id=self._project_id,
|
|
324
|
+
subject_name=subject_name,
|
|
325
|
+
session_id=str(ssid),
|
|
326
|
+
input_data_type=input_data_type,
|
|
327
|
+
split_data=split_data,
|
|
328
|
+
add_to_container_id=add_to_container_id,
|
|
329
|
+
date_of_scan=date_of_scan,
|
|
330
|
+
description=description,
|
|
331
|
+
name=name,
|
|
332
|
+
),
|
|
333
|
+
anonymise=False, # will be anonymised in the upload tool.
|
|
334
|
+
chunk_size=chunk_size,
|
|
335
|
+
max_retries=max_retries,
|
|
336
|
+
)
|
|
324
337
|
|
|
325
|
-
|
|
326
|
-
if file_size == 0:
|
|
327
|
-
logger.error("Cannot upload empty file {}".format(file_path))
|
|
328
|
-
return False
|
|
329
|
-
uploaded = 0
|
|
330
|
-
session_id = self.__get_session_id(file_path)
|
|
331
|
-
chunk_num = 0
|
|
332
|
-
retries_count = 0
|
|
333
|
-
uploaded_bytes = 0
|
|
334
|
-
response = None
|
|
335
|
-
last_chunk = False
|
|
336
|
-
error_message = None
|
|
337
|
-
|
|
338
|
-
while True:
|
|
339
|
-
data = file_object.read(chunk_size)
|
|
340
|
-
if not data:
|
|
341
|
-
break
|
|
342
|
-
|
|
343
|
-
start_position = chunk_num * chunk_size
|
|
344
|
-
end_position = start_position + chunk_size - 1
|
|
345
|
-
bytes_to_send = chunk_size
|
|
346
|
-
|
|
347
|
-
if end_position >= total_bytes:
|
|
348
|
-
last_chunk = True
|
|
349
|
-
end_position = total_bytes - 1
|
|
350
|
-
bytes_to_send = total_bytes - uploaded_bytes
|
|
351
|
-
|
|
352
|
-
bytes_range = (
|
|
353
|
-
"bytes "
|
|
354
|
-
+ str(start_position)
|
|
355
|
-
+ "-"
|
|
356
|
-
+ str(end_position)
|
|
357
|
-
+ "/"
|
|
358
|
-
+ str(total_bytes)
|
|
359
|
-
)
|
|
338
|
+
single_upload.start()
|
|
360
339
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
data,
|
|
364
|
-
bytes_range,
|
|
365
|
-
bytes_to_send,
|
|
366
|
-
session_id,
|
|
367
|
-
dispstr,
|
|
368
|
-
last_chunk,
|
|
369
|
-
name,
|
|
370
|
-
date_of_scan,
|
|
371
|
-
description,
|
|
372
|
-
subject_name,
|
|
373
|
-
ssid,
|
|
374
|
-
filename,
|
|
375
|
-
input_data_type,
|
|
376
|
-
result,
|
|
377
|
-
add_to_container_id,
|
|
378
|
-
split_data,
|
|
379
|
-
mock_response=mock_response,
|
|
380
|
-
)
|
|
381
|
-
if response is None:
|
|
382
|
-
retries_count += 1
|
|
383
|
-
time.sleep(retries_count * 5)
|
|
384
|
-
if retries_count > max_retries:
|
|
385
|
-
error_message = "HTTP Connection Problem"
|
|
386
|
-
break
|
|
387
|
-
elif int(response.status_code) == 201:
|
|
388
|
-
chunk_num += 1
|
|
389
|
-
retries_count = 0
|
|
390
|
-
uploaded_bytes += chunk_size
|
|
391
|
-
elif int(response.status_code) == 200:
|
|
392
|
-
self.__show_progress(file_size, file_size, finish=True)
|
|
393
|
-
break
|
|
394
|
-
elif int(response.status_code) == 416:
|
|
395
|
-
retries_count += 1
|
|
396
|
-
time.sleep(retries_count * 5)
|
|
397
|
-
if retries_count > self.max_retries:
|
|
398
|
-
error_message = (
|
|
399
|
-
"Error Code: 416; Requested Range "
|
|
400
|
-
"Not Satisfiable (NGINX)"
|
|
401
|
-
)
|
|
402
|
-
break
|
|
403
|
-
else:
|
|
404
|
-
retries_count += 1
|
|
405
|
-
time.sleep(retries_count * 5)
|
|
406
|
-
if retries_count > max_retries:
|
|
407
|
-
error_message = (
|
|
408
|
-
"Number of retries has been reached. "
|
|
409
|
-
"Upload process stops here !"
|
|
410
|
-
)
|
|
411
|
-
break
|
|
412
|
-
|
|
413
|
-
uploaded += chunk_size
|
|
414
|
-
self.__show_progress(uploaded, file_size)
|
|
415
|
-
if error_message is not None:
|
|
416
|
-
raise Exception(error_message)
|
|
417
|
-
try:
|
|
418
|
-
platform.parse_response(response)
|
|
419
|
-
except errors.PlatformError as error:
|
|
420
|
-
logger.error(error)
|
|
340
|
+
if single_upload.status == UploadStatus.FAILED: # FAILED
|
|
341
|
+
print("Upload Failed!")
|
|
421
342
|
return False
|
|
422
343
|
|
|
423
344
|
message = (
|
|
424
345
|
"Your data was successfully uploaded. "
|
|
425
346
|
"The uploaded file will be soon processed !"
|
|
426
347
|
)
|
|
427
|
-
|
|
348
|
+
print(message)
|
|
428
349
|
return True
|
|
429
350
|
|
|
430
351
|
def delete_file(self, container_id, filenames):
|
|
@@ -516,7 +437,7 @@ class Project:
|
|
|
516
437
|
return False
|
|
517
438
|
|
|
518
439
|
def download_file(
|
|
519
|
-
self, container_id, file_name, local_filename=
|
|
440
|
+
self, container_id, file_name, local_filename=None, overwrite=False
|
|
520
441
|
):
|
|
521
442
|
"""
|
|
522
443
|
Download a single file from a specific container.
|
|
@@ -527,7 +448,7 @@ class Project:
|
|
|
527
448
|
ID of the container inside which the file is.
|
|
528
449
|
file_name : str
|
|
529
450
|
Name of the file in the container.
|
|
530
|
-
local_filename : str
|
|
451
|
+
local_filename : str, optional
|
|
531
452
|
Name of the file to be created. By default, the same as file_name.
|
|
532
453
|
overwrite : bool
|
|
533
454
|
Whether to overwrite the file if existing.
|
|
@@ -724,7 +645,7 @@ class Project:
|
|
|
724
645
|
return self.get_subjects_metadata()
|
|
725
646
|
|
|
726
647
|
@property
|
|
727
|
-
def metadata_parameters(self):
|
|
648
|
+
def metadata_parameters(self) -> Union[Dict, None]:
|
|
728
649
|
"""
|
|
729
650
|
List all the parameters in the subject-level metadata.
|
|
730
651
|
|
|
@@ -735,16 +656,20 @@ class Project:
|
|
|
735
656
|
modification of these subject-level metadata parameters via the
|
|
736
657
|
'change_subject_metadata()' method.
|
|
737
658
|
|
|
659
|
+
Example:
|
|
660
|
+
dictionary {"param_name":
|
|
661
|
+
{ "order": Int,
|
|
662
|
+
"tags": [tag1, tag2, ..., ],
|
|
663
|
+
"title: "Title",
|
|
664
|
+
"type": "integer|string|date|list|decimal",
|
|
665
|
+
"visible": 0|1
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
738
669
|
Returns
|
|
739
670
|
-------
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
{ "order": Int,
|
|
743
|
-
"tags": [tag1, tag2, ..., ],
|
|
744
|
-
"title: "Title",
|
|
745
|
-
"type": "integer|string|date|list|decimal",
|
|
746
|
-
"visible": 0|1
|
|
747
|
-
}}
|
|
671
|
+
metadata_parameters : dict[str] or None
|
|
672
|
+
|
|
748
673
|
"""
|
|
749
674
|
logger = logging.getLogger(logger_name)
|
|
750
675
|
try:
|
|
@@ -1055,7 +980,10 @@ class Project:
|
|
|
1055
980
|
f"either all starting with 'md_' or none."
|
|
1056
981
|
)
|
|
1057
982
|
|
|
1058
|
-
metadata_keys =
|
|
983
|
+
metadata_keys = []
|
|
984
|
+
if self.metadata_parameters:
|
|
985
|
+
metadata_keys = self.metadata_parameters.keys()
|
|
986
|
+
|
|
1059
987
|
if not all(
|
|
1060
988
|
(
|
|
1061
989
|
key[3:] in metadata_keys
|
|
@@ -1279,7 +1207,7 @@ class Project:
|
|
|
1279
1207
|
|
|
1280
1208
|
Returns
|
|
1281
1209
|
-------
|
|
1282
|
-
dict
|
|
1210
|
+
dict or bool
|
|
1283
1211
|
Dictionary with the metadata. False otherwise.
|
|
1284
1212
|
"""
|
|
1285
1213
|
all_metadata = self.list_container_files_metadata(container_id)
|
|
@@ -1591,7 +1519,7 @@ class Project:
|
|
|
1591
1519
|
def list_container_files(
|
|
1592
1520
|
self,
|
|
1593
1521
|
container_id,
|
|
1594
|
-
):
|
|
1522
|
+
) -> Any:
|
|
1595
1523
|
"""
|
|
1596
1524
|
List the name of the files available inside a given container.
|
|
1597
1525
|
Parameters
|
|
@@ -1674,7 +1602,7 @@ class Project:
|
|
|
1674
1602
|
selected_files.append(file)
|
|
1675
1603
|
return selected_files
|
|
1676
1604
|
|
|
1677
|
-
def list_container_files_metadata(self, container_id):
|
|
1605
|
+
def list_container_files_metadata(self, container_id) -> dict:
|
|
1678
1606
|
"""
|
|
1679
1607
|
List all the metadata of the files available inside a given container.
|
|
1680
1608
|
|
|
@@ -1705,7 +1633,7 @@ class Project:
|
|
|
1705
1633
|
|
|
1706
1634
|
""" Analysis Related Methods """
|
|
1707
1635
|
|
|
1708
|
-
def get_analysis(self, analysis_name_or_id):
|
|
1636
|
+
def get_analysis(self, analysis_name_or_id) -> dict:
|
|
1709
1637
|
"""
|
|
1710
1638
|
Returns the analysis corresponding with the analysis id or analysis
|
|
1711
1639
|
name
|
|
@@ -2944,20 +2872,6 @@ class Project:
|
|
|
2944
2872
|
modalities.append(modality)
|
|
2945
2873
|
return modalities
|
|
2946
2874
|
|
|
2947
|
-
def __show_progress(self, done, total, finish=False):
|
|
2948
|
-
bytes_in_mb = 1024 * 1024
|
|
2949
|
-
progress_message = "\r[{:.2f} %] Uploaded {:.2f} of {:.2f} Mb".format(
|
|
2950
|
-
done / float(total) * 100, done / bytes_in_mb, total / bytes_in_mb
|
|
2951
|
-
)
|
|
2952
|
-
sys.stdout.write(progress_message)
|
|
2953
|
-
sys.stdout.flush()
|
|
2954
|
-
if not finish:
|
|
2955
|
-
pass
|
|
2956
|
-
# sys.stdout.write("")
|
|
2957
|
-
# sys.stdout.flush()
|
|
2958
|
-
else:
|
|
2959
|
-
sys.stdout.write("\n")
|
|
2960
|
-
|
|
2961
2875
|
def __get_session_id(self, file_path):
|
|
2962
2876
|
m = hashlib.md5()
|
|
2963
2877
|
m.update(file_path.encode("utf-8"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|