qmenta-client 2.1.dev1513__tar.gz → 2.1.dev1514__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qmenta-client
3
- Version: 2.1.dev1513
3
+ Version: 2.1.dev1514
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 (>=4.0.1,<5.0.0)
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.dev1513"
3
+ version = "2.1.dev1514"
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 = "^4.0.1"
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.10",
267
+ input_data_type="qmenta_medical_image_data:3.11.3",
266
268
  add_to_container_id=0,
267
- chunk_size=2**9,
268
- max_retries=10,
269
+ chunk_size=2**24, # Optimized for GCS Bucket Storage
270
+ max_retries=5,
269
271
  split_data=False,
270
- mock_response=False,
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
- filename = os.path.split(file_path)[1]
309
- input_data_type = "offline_analysis:1.0" if result else input_data_type
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
- # making chunks of the file and sending one by one
322
- logger = logging.getLogger(logger_name)
323
- with open(file_path, "rb") as file_object:
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
- file_size = os.path.getsize(file_path)
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
- dispstr = f"attachment; filename={filename}"
362
- response = self._upload_chunk(
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
- logger.info(message)
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=False, overwrite=False
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
- dict[str] -> dict[str] -> x
741
- dictionary {"param_name":
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 = self.metadata_parameters.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"))