seer-pas-sdk 1.0.0__py3-none-any.whl → 1.1.1__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.
seer_pas_sdk/core/sdk.py CHANGED
@@ -1,17 +1,21 @@
1
1
  from tqdm import tqdm
2
2
 
3
3
  import deprecation
4
+ import numpy as np
4
5
  import os
5
- import jwt
6
6
  import requests
7
7
  import urllib.request
8
8
  import ssl
9
9
 
10
+
10
11
  from typing import List as _List, Tuple as _Tuple
11
12
 
12
13
  from ..common import *
13
14
  from ..auth import Auth
14
15
  from ..objects.volcanoplot import VolcanoPlotBuilder
16
+ from ..objects.headers import *
17
+
18
+ import warnings
15
19
 
16
20
 
17
21
  class SeerSDK:
@@ -31,7 +35,7 @@ class SeerSDK:
31
35
  try:
32
36
  self._auth = Auth(username, password, instance)
33
37
 
34
- self._auth.get_token()
38
+ self._auth._login()
35
39
  print(f"User '{username}' logged in.\n")
36
40
 
37
41
  if not tenant:
@@ -45,16 +49,43 @@ class SeerSDK:
45
49
  print("Logging into home tenant...")
46
50
  # If an error occurs while directing the user to a tenant, default to home tenant.
47
51
  print(f"You are now active in {self.get_active_tenant_name()}")
52
+ except ServerError as e:
53
+ raise e
48
54
  except Exception as e:
49
55
  raise ValueError(
50
56
  f"Could not log in.\nPlease check your credentials and/or instance: {e}."
51
57
  )
52
58
 
53
- def _get_auth_headers(self, use_multi_tenant=True):
59
+ def logout(self):
60
+ """
61
+ Perform a logout operation for the current user of the SDK instance.
62
+
63
+ Returns
64
+ -------
65
+ success : bool
66
+ Boolean denoting whether the logout operation was successful.
67
+ """
68
+ if self._auth.has_valid_token():
69
+ self._auth._logout()
70
+ return True
71
+ else:
72
+ print("The user's session is expired. No action taken.")
73
+ return False
74
+
75
+ def __del__(self):
76
+ """
77
+ Destructor for the SeerSDK class. Logs out the user when the object is deleted.
78
+ """
79
+ if self._auth.has_valid_token():
80
+ self._auth._logout()
81
+
82
+ def _get_auth_headers(self, caller, use_multi_tenant=True):
54
83
  id_token, access_token = self._auth.get_token()
55
84
  header = {
56
85
  "Authorization": id_token,
57
86
  "Access-Token": access_token,
87
+ "x-seer-source": "sdk",
88
+ "x-seer-id": f"{self._auth.version}/{caller}",
58
89
  }
59
90
  if use_multi_tenant:
60
91
  multi_tenant = {
@@ -64,10 +95,10 @@ class SeerSDK:
64
95
  header.update(multi_tenant)
65
96
  return header
66
97
 
67
- def _get_auth_session(self, use_multi_tenant=True):
98
+ def _get_auth_session(self, caller, use_multi_tenant=True):
68
99
  sess = requests.Session()
69
100
 
70
- sess.headers.update(self._get_auth_headers(use_multi_tenant))
101
+ sess.headers.update(self._get_auth_headers(caller, use_multi_tenant))
71
102
 
72
103
  return sess
73
104
 
@@ -80,7 +111,7 @@ class SeerSDK:
80
111
  response : list[dict]
81
112
  A list of tenant objects pertaining to the user.
82
113
  """
83
- with self._get_auth_session() as s:
114
+ with self._get_auth_session("getusertenants") as s:
84
115
  response = s.get(f"{self._auth.url}api/v1/usertenants")
85
116
 
86
117
  if response.status_code != 200:
@@ -162,7 +193,7 @@ class SeerSDK:
162
193
  "Invalid tenant identifier. Tenant was not switched."
163
194
  )
164
195
 
165
- with self._get_auth_session() as s:
196
+ with self._get_auth_session("switchtenant") as s:
166
197
  response = s.put(
167
198
  self._auth.url + "api/v1/users/tenant",
168
199
  json={
@@ -242,7 +273,7 @@ class SeerSDK:
242
273
 
243
274
  URL = f"{self._auth.url}api/v1/usergroups"
244
275
 
245
- with self._get_auth_session() as s:
276
+ with self._get_auth_session("getspaces") as s:
246
277
  spaces = s.get(URL)
247
278
 
248
279
  if spaces.status_code != 200:
@@ -251,6 +282,11 @@ class SeerSDK:
251
282
  )
252
283
  return spaces.json()
253
284
 
285
+ @deprecation.deprecated(
286
+ deprecated_in="1.1.0",
287
+ removed_in="2.0.0",
288
+ details="This method is deprecated and will be removed in a future release. Use `find_plates` instead.",
289
+ )
254
290
  def get_plates(
255
291
  self, plate_id: str = None, plate_name: str = None, as_df: bool = False
256
292
  ):
@@ -293,7 +329,7 @@ class SeerSDK:
293
329
  938 5b05d440-6610-11ea-96e3-d5a4dab4ebf6 ... None
294
330
  939 9872e3f0-544e-11ea-ad9e-1991e0725494 ... None
295
331
 
296
- >>> seer_sdk.get_plate_metadata(id="YOUR_PLATE_ID_HERE")
332
+ >>> seer_sdk.get_plates(id="YOUR_PLATE_ID_HERE")
297
333
  >>> [{ "id": ... }]
298
334
  """
299
335
 
@@ -307,7 +343,7 @@ class SeerSDK:
307
343
  else:
308
344
  params = dict()
309
345
 
310
- with self._get_auth_session() as s:
346
+ with self._get_auth_session("getplates") as s:
311
347
 
312
348
  plates = s.get(
313
349
  f"{URL}/{plate_id}" if plate_id else URL,
@@ -327,6 +363,172 @@ class SeerSDK:
327
363
 
328
364
  return res if not as_df else dict_to_df(res)
329
365
 
366
+ def get_plate(self, plate_id: str = None, plate_name: str = None):
367
+ """
368
+ Fetches a plate.
369
+
370
+ Parameters
371
+ ----------
372
+ plate_id : str, optional
373
+ ID of the plate to be fetched, defaulted to None.
374
+
375
+ plate_name : str, optional
376
+ Name of the plate to be fetched, defaulted to None.
377
+
378
+ Returns
379
+ -------
380
+ plate: dict
381
+ A plate object.
382
+ """
383
+ if not (bool(plate_id) ^ bool(plate_name)):
384
+ raise ValueError(
385
+ "You must provide either plate_id or plate_name, but not both."
386
+ )
387
+
388
+ if plate_id:
389
+ URL = f"{self._auth.url}api/v1/plates/{plate_id}"
390
+ with self._get_auth_session("getplate") as s:
391
+ plates = s.get(URL)
392
+ if plates.status_code != 200:
393
+ raise ValueError(
394
+ "Invalid request. Please check your parameters."
395
+ )
396
+ res = plates.json()
397
+ spaces = {
398
+ x["id"]: x["usergroup_name"] for x in self.get_spaces()
399
+ }
400
+ if "tenant_id" in res:
401
+ del res["tenant_id"]
402
+ if "user_group" in res:
403
+ res["space"] = spaces.get(res["user_group"], "General")
404
+ del res["user_group"]
405
+ if "id" in res:
406
+ res["plate_uuid"] = res["id"]
407
+ return res
408
+ else:
409
+ res = self.find_plates(plate_name=plate_name)
410
+ if not res:
411
+ raise ValueError(f"No plate found with name '{plate_name}'.")
412
+ elif len(res) > 1:
413
+ raise ValueError(
414
+ f"Multiple plates found with name '{plate_name}'. Please specify a plate_id."
415
+ )
416
+ else:
417
+ return res[0]
418
+
419
+ def find_plates(
420
+ self,
421
+ plate_id: str = None,
422
+ plate_name: str = None,
423
+ project_id: str = None,
424
+ project_name: str = None,
425
+ as_df: bool = False,
426
+ ):
427
+ """
428
+ Fetches a list of plates for the authenticated user. If no `plate_id` is provided, returns all plates for the authenticated user. If `plate_id` is provided, returns the plate with the given `plate_id`, provided it exists.
429
+
430
+ Parameters
431
+ ----------
432
+ plate_id : str, optional
433
+ ID of the plate to be fetched, defaulted to None.
434
+ plate_name : str, optional
435
+ Name of the plate to be fetched, defaulted to None.
436
+ project_id: str, optional
437
+ ID of the project to filter plates by, defaulted to None.
438
+ project_name : str, optional
439
+ Name of the project to filter plates by, defaulted to None.
440
+ as_df: bool
441
+ whether the result should be converted to a DataFrame, defaulted to None.
442
+
443
+ Returns
444
+ -------
445
+ plates: list[dict] or DataFrame
446
+ List/DataFrame of plate objects for the authenticated user.
447
+
448
+ Examples
449
+ -------
450
+ >>> from seer_pas_sdk import SeerSDK
451
+ >>> seer_sdk = SeerSDK()
452
+ >>> seer_sdk.find_plates()
453
+ >>> [
454
+ { "id": ... },
455
+ { "id": ... },
456
+ ...
457
+ ]
458
+ >>> seer_sdk.find_plates(as_df=True)
459
+ >>> id ... user_group
460
+ 0 a7c12190-15da-11ee-bdf1-bbaa73585acf ... None
461
+ 1 8c3b1480-15da-11ee-bdf1-bbaa73585acf ... None
462
+ 2 6f158840-15da-11ee-bdf1-bbaa73585acf ... None
463
+ 3 1a8a2920-15da-11ee-bdf1-bbaa73585acf ... None
464
+ 4 7ab47f40-15d9-11ee-bdf1-bbaa73585acf ... None
465
+ .. ... ... ...
466
+ 935 8fa91c00-6621-11ea-96e3-d5a4dab4ebf6 ... None
467
+ 936 53180b20-6621-11ea-96e3-d5a4dab4ebf6 ... None
468
+ 937 5c31fe90-6618-11ea-96e3-d5a4dab4ebf6 ... None
469
+ 938 5b05d440-6610-11ea-96e3-d5a4dab4ebf6 ... None
470
+ 939 9872e3f0-544e-11ea-ad9e-1991e0725494 ... None
471
+
472
+ >>> seer_sdk.find_plates(id="YOUR_PLATE_ID_HERE")
473
+ >>> [{ "id": ... }]
474
+ """
475
+
476
+ URL = f"{self._auth.url}api/v1/plates"
477
+ res = []
478
+
479
+ if project_id or project_name:
480
+ if project_name and not project_id:
481
+ samples = self.find_samples(
482
+ project_name=project_name, as_df=False
483
+ )
484
+ else:
485
+ samples = self.find_samples(project_id=project_id, as_df=False)
486
+ plate_ids = {
487
+ x.get("plate_uuid") for x in samples if "plate_uuid" in x
488
+ }
489
+ res = [self.get_plate(plate_id=x) for x in plate_ids]
490
+ return res if not as_df else dict_to_df(res)
491
+
492
+ if not plate_id and not plate_name:
493
+ params = {"all": "true"}
494
+ elif plate_id:
495
+ params = {"searchFields": "id", "searchItem": plate_id}
496
+ elif plate_name:
497
+ params = {"searchFields": "plate_name", "searchItem": plate_name}
498
+ else:
499
+ params = dict()
500
+
501
+ spaces = {x["id"]: x["usergroup_name"] for x in self.get_spaces()}
502
+ with self._get_auth_session("findplates") as s:
503
+
504
+ plates = s.get(
505
+ URL,
506
+ params=params,
507
+ )
508
+ if plates.status_code != 200:
509
+ raise ValueError(
510
+ "Invalid request. Please check your parameters."
511
+ )
512
+
513
+ res = plates.json()["data"]
514
+
515
+ for entry in res:
516
+ if "tenant_id" in entry:
517
+ del entry["tenant_id"]
518
+ if "user_group" in entry:
519
+ entry["space"] = spaces.get(entry["user_group"], "General")
520
+ del entry["user_group"]
521
+ if "id" in entry:
522
+ entry["plate_uuid"] = entry["id"]
523
+ if (not res) and as_df:
524
+ return pd.DataFrame(columns=PLATE_COLUMNS)
525
+ return res if not as_df else dict_to_df(res)
526
+
527
+ @deprecation.deprecated(
528
+ deprecated_in="1.1.0",
529
+ removed_in="2.0.0",
530
+ details="This method is deprecated and will be removed in a future release. Use `find_projects` instead.",
531
+ )
330
532
  def get_projects(
331
533
  self,
332
534
  project_id: str = None,
@@ -393,7 +595,7 @@ class SeerSDK:
393
595
  else:
394
596
  params = {"searchFields": "id", "searchItem": project_id}
395
597
 
396
- with self._get_auth_session() as s:
598
+ with self._get_auth_session("getprojects") as s:
397
599
 
398
600
  projects = s.get(URL, params=params)
399
601
  if projects.status_code != 200:
@@ -416,8 +618,172 @@ class SeerSDK:
416
618
  entry["raw_file_path"] = entry["raw_file_path"][
417
619
  location(entry["raw_file_path"]) :
418
620
  ]
621
+
622
+ return res if not as_df else dict_to_df(res)
623
+
624
+ def get_project(self, project_id: str = None, project_name: str = None):
625
+ """
626
+ Fetches a project.
627
+
628
+ Parameters
629
+ ----------
630
+ project_id: str, optional
631
+ ID of the project to be fetched, defaulted to None.
632
+
633
+ project_name: str, optional
634
+ Name of the project to be fetched, defaulted to None.
635
+
636
+ Returns
637
+ -------
638
+ projects: dict
639
+ A project object.
640
+ """
641
+ if not (bool(project_id) ^ bool(project_name)):
642
+ raise ValueError(
643
+ "You must provide either project_id or project_name, but not both."
644
+ )
645
+
646
+ if project_id:
647
+ URL = f"{self._auth.url}api/v1/projects/{project_id}"
648
+ with self._get_auth_session("getproject") as s:
649
+ projects = s.get(URL)
650
+ if projects.status_code != 200:
651
+ raise ValueError(
652
+ "Invalid request. Please check your parameters."
653
+ )
654
+ res = projects.json()
655
+ spaces = {
656
+ x["id"]: x["usergroup_name"] for x in self.get_spaces()
657
+ }
658
+ if "tenant_id" in res:
659
+ del res["tenant_id"]
660
+ if "user_group" in res:
661
+ res["space"] = spaces.get(res["user_group"], "General")
662
+ del res["user_group"]
663
+ plate_ids = {
664
+ x["plate_uuid"]
665
+ for x in self.find_samples(project_id=res["id"])
666
+ }
667
+ res["plate_uuids"] = list(plate_ids)
668
+ return res
669
+ else:
670
+ res = self.find_projects(project_name=project_name)
671
+ if not res:
672
+ raise ValueError(
673
+ f"No project found with name '{project_name}'."
674
+ )
675
+ elif len(res) > 1:
676
+ raise ValueError(
677
+ f"Multiple projects found with name '{project_name}'. Please specify a project_id."
678
+ )
679
+ else:
680
+ return res[0]
681
+
682
+ def find_projects(
683
+ self,
684
+ project_id: str = None,
685
+ project_name: str = None,
686
+ as_df: bool = False,
687
+ ):
688
+ """
689
+ Fetches a list of projects. If no `project_id` is provided, returns all projects. If `project_id` is provided, returns the project with the given `project_id`, provided it exists.
690
+
691
+ Parameters
692
+ ----------
693
+ project_id: str, optional
694
+ Project ID of the project to be fetched, defaulted to None.
695
+ as_df: bool
696
+ whether the result should be converted to a DataFrame, defaulted to False.
697
+
698
+ Returns
699
+ -------
700
+ projects: list[dict] or DataFrame
701
+ DataFrame or list of project objects.
702
+
703
+ Examples
704
+ -------
705
+ >>> from seer_pas_sdk import SeerSDK
706
+ >>> seer_sdk = SeerSDK()
707
+ >>> seer_sdk.get_projects()
708
+ >>> [
709
+ { "project_name": ... },
710
+ { "project_name": ... },
711
+ ...
712
+ ]
713
+
714
+ >>> seer_sdk.get_projects(as_df=True)
715
+ >>> id ... user_group
716
+ 0 a7c12190-15da-11ee-bdf1-bbaa73585acf ... None
717
+ 1 8c3b1480-15da-11ee-bdf1-bbaa73585acf ... None
718
+ 2 6f158840-15da-11ee-bdf1-bbaa73585acf ... None
719
+ 3 1a8a2920-15da-11ee-bdf1-bbaa73585acf ... None
720
+ 4 7ab47f40-15d9-11ee-bdf1-bbaa73585acf ... None
721
+ .. ... ... ...
722
+ 935 8fa91c00-6621-11ea-96e3-d5a4dab4ebf6 ... None
723
+ 936 53180b20-6621-11ea-96e3-d5a4dab4ebf6 ... None
724
+ 937 5c31fe90-6618-11ea-96e3-d5a4dab4ebf6 ... None
725
+ 938 5b05d440-6610-11ea-96e3-d5a4dab4ebf6 ... None
726
+ 939 9872e3f0-544e-11ea-ad9e-1991e0725494 ... None
727
+
728
+ >>> seer_sdk.find_projects(project_id="YOUR_PROJECT_ID_HERE")
729
+ >>> [{ "project_name": ... }]
730
+ """
731
+
732
+ URL = f"{self._auth.url}api/v1/projects"
733
+ res = []
734
+ if not project_id and not project_name:
735
+ params = {"all": "true"}
736
+ elif project_name:
737
+ params = {
738
+ "searchFields": "project_name",
739
+ "searchItem": project_name,
740
+ }
741
+ else:
742
+ params = {"searchFields": "id", "searchItem": project_id}
743
+
744
+ with self._get_auth_session("findprojects") as s:
745
+
746
+ projects = s.get(URL, params=params)
747
+ if projects.status_code != 200:
748
+ raise ValueError(
749
+ "Invalid request. Please check your parameters."
750
+ )
751
+ res = projects.json()["data"]
752
+
753
+ spaces = {x["id"]: x["usergroup_name"] for x in self.get_spaces()}
754
+ plate_name_to_plate_uuid = {
755
+ x["plate_name"]: x["plate_uuid"]
756
+ for x in self.find_plates(as_df=False)
757
+ }
758
+ for entry in res:
759
+ if "tenant_id" in entry:
760
+ del entry["tenant_id"]
761
+
762
+ if "raw_file_path" in entry:
763
+ # Simple lambda function to find the third occurrence of '/' in the raw file path
764
+ location = lambda s: len(s) - len(s.split("/", 3)[-1])
765
+ # Slicing the string from the location
766
+ entry["raw_file_path"] = entry["raw_file_path"][
767
+ location(entry["raw_file_path"]) :
768
+ ]
769
+ if "user_group" in entry:
770
+ entry["space"] = spaces.get(entry["user_group"], "General")
771
+ del entry["user_group"]
772
+
773
+ if "plates" in entry:
774
+ entry["plate_uuids"] = [
775
+ plate_name_to_plate_uuid[x] for x in entry["plates"]
776
+ ]
777
+
778
+ if not res and as_df:
779
+ return pd.DataFrame(columns=PROJECT_COLUMNS)
419
780
  return res if not as_df else dict_to_df(res)
420
781
 
782
+ @deprecation.deprecated(
783
+ deprecated_in="1.1.0",
784
+ removed_in="2.0.0",
785
+ details="This method is deprecated and will be removed in a future release. Use `find_samples` instead.",
786
+ )
421
787
  def get_samples(
422
788
  self,
423
789
  plate_id: str = None,
@@ -493,10 +859,10 @@ class SeerSDK:
493
859
  sample_params = {"all": "true"}
494
860
 
495
861
  if project_id or plate_id:
496
- with self._get_auth_session() as s:
862
+ with self._get_auth_session("getsamples") as s:
497
863
  if plate_id:
498
864
  try:
499
- self.get_plates(plate_id)
865
+ self.find_plates(plate_id=plate_id)
500
866
  except:
501
867
  raise ValueError("Plate ID is invalid.")
502
868
  sample_params["plateId"] = plate_id
@@ -530,7 +896,7 @@ class SeerSDK:
530
896
  )
531
897
  else:
532
898
  res_df = self._get_analysis_samples(
533
- analysis_name=analysis_name, as_df=True, is_name=True
899
+ analysis_name=analysis_name, as_df=True
534
900
  )
535
901
 
536
902
  # apply post processing
@@ -549,26 +915,198 @@ class SeerSDK:
549
915
 
550
916
  return res_df.to_dict(orient="records") if not as_df else res_df
551
917
 
552
- def _filter_samples_metadata(
918
+ def find_samples(
553
919
  self,
554
- project_id: str,
555
- filter: str,
556
- sample_ids: list = None,
920
+ plate_id: str = None,
921
+ project_id: str = None,
922
+ project_name: str = None,
923
+ analysis_id: str = None,
924
+ analysis_name: str = None,
925
+ as_df: bool = False,
557
926
  ):
558
927
  """
559
- ****************
560
- [UNEXPOSED METHOD CALL]
561
- ****************
562
- Get samples given a filter and project_id.
928
+ Fetches a list of samples. If no parameters are provided, returns all samples. If `plate_id` or `project_id` is provided, returns samples associated with that plate or project. If `analysis_id` or `analysis_name` is provided, returns samples associated with that analysis.
563
929
 
564
930
  Parameters
565
931
  ----------
566
- project_id : str
567
- The project id.
568
- filter : str
569
- The filter to be applied. Acceptable values are 'control' or 'sample'.
570
- sample_ids : list, optional
571
- List of user provided sample ids
932
+ plate_id : str, optional
933
+ ID of the plate for which samples are to be fetched, defaulted to None.
934
+ plate_name : str, optional
935
+ Name of the plate to be fetched, defaulted to None.
936
+ project_id : str, optional
937
+ ID of the project for which samples are to be fetched, defaulted to None.
938
+ project_name : str, optional
939
+ Name of the project to filter plates by, defaulted to None.
940
+ analysis_id : str, optional
941
+ ID of the analysis for which samples are to be fetched, defaulted to None.
942
+ analysis_name : str, optional
943
+ Name of the analysis for which samples are to be fetched, defaulted to None.
944
+ as_df: bool
945
+ whether the result should be converted to a DataFrame, defaulted to False.
946
+
947
+ Returns
948
+ -------
949
+ samples: list[dict] or DataFrame
950
+ List/DataFrame of samples for the authenticated user.
951
+
952
+ Examples
953
+ -------
954
+ >>> from seer_pas_sdk import SeerSDK
955
+ >>> seer_sdk = SeerSDK()
956
+
957
+ >>> seer_sdk.find_samples(plate_id="7ec8cad0-15e0-11ee-bdf1-bbaa73585acf")
958
+ >>> [
959
+ { "id": ... },
960
+ { "id": ... },
961
+ ...
962
+ ]
963
+
964
+ >>> seer_sdk.find_samples(as_df=True)
965
+ >>> id ... control
966
+ 0 812139c0-15e0-11ee-bdf1-bbaa73585acf ...
967
+ 1 803e05b0-15e0-11ee-bdf1-bbaa73585acf ... MPE Control
968
+ 2 a9b26a40-15da-11ee-bdf1-bbaa73585acf ...
969
+ 3 a8fc87c0-15da-11ee-bdf1-bbaa73585acf ... MPE Control
970
+ 4 8e322990-15da-11ee-bdf1-bbaa73585acf ...
971
+ ... ... ... ...
972
+ 3624 907e1f40-6621-11ea-96e3-d5a4dab4ebf6 ... C132
973
+ 3625 53e59450-6621-11ea-96e3-d5a4dab4ebf6 ... C132
974
+ 3626 5d11b030-6618-11ea-96e3-d5a4dab4ebf6 ... C132
975
+ 3627 5bdf9270-6610-11ea-96e3-d5a4dab4ebf6 ... C132
976
+ 3628 dd607ef0-654c-11ea-8eb2-25a1cfd1163c ... C132
977
+ """
978
+
979
+ # Raise an error if none or more than one of the primary key parameters are passed in.
980
+ if (
981
+ sum(
982
+ [
983
+ True if x else False
984
+ for x in [plate_id, project_id, analysis_id, analysis_name]
985
+ ]
986
+ )
987
+ > 1
988
+ ):
989
+ raise ValueError(
990
+ "You must pass in no more than one of plate_id, project_id, analysis_id, analysis_name."
991
+ )
992
+
993
+ res = []
994
+ URL = f"{self._auth.url}api/v1/samples"
995
+
996
+ if project_name and not project_id:
997
+ projects = self.find_projects(project_name=project_name)
998
+ if not projects or len(projects) > 1:
999
+ raise ValueError(
1000
+ f"Project name '{project_name}' is invalid or ambiguous. Please specify a project_id."
1001
+ )
1002
+ project_id = projects[0]["id"]
1003
+
1004
+ sample_params = {"all": "true"}
1005
+
1006
+ if project_id or plate_id:
1007
+ with self._get_auth_session("findsamples") as s:
1008
+ if plate_id:
1009
+ search_name = "plate_id"
1010
+ search_value = plate_id
1011
+ sample_params["plateId"] = plate_id
1012
+
1013
+ else:
1014
+ search_name = "project_id"
1015
+ search_value = project_id
1016
+ sample_params["projectId"] = project_id
1017
+
1018
+ samples = s.get(URL, params=sample_params)
1019
+ if samples.status_code != 200:
1020
+ raise ValueError(
1021
+ f"Failed to fetch sample data for {search_name}: {search_value}."
1022
+ )
1023
+ res = samples.json()["data"]
1024
+ if not res:
1025
+ return (
1026
+ [] if not as_df else pd.DataFrame(columns=SAMPLE_COLUMNS)
1027
+ )
1028
+ res_df = dict_to_df(res)
1029
+
1030
+ # API returns empty strings if not a control, replace with None for filtering purposes
1031
+ res_df["control"] = res_df["control"].apply(
1032
+ lambda x: x if x else None
1033
+ )
1034
+ elif analysis_id or analysis_name:
1035
+ if analysis_id:
1036
+ res_df = self._get_analysis_samples(
1037
+ analysis_id=analysis_id, as_df=True
1038
+ )
1039
+ else:
1040
+ res_df = self._get_analysis_samples(
1041
+ analysis_name=analysis_name, as_df=True
1042
+ )
1043
+ else:
1044
+ with self._get_auth_session("findsamples") as s:
1045
+ samples = s.get(URL, params=sample_params)
1046
+ if samples.status_code != 200:
1047
+ raise ValueError(
1048
+ "Failed to fetch sample data for the authenticated user."
1049
+ )
1050
+ res = samples.json()["data"]
1051
+ res_df = dict_to_df(res)
1052
+
1053
+ if res_df.empty and as_df:
1054
+ return pd.DataFrame(columns=SAMPLE_COLUMNS)
1055
+
1056
+ plate_uuid_to_id = {
1057
+ x["plate_uuid"]: x["plate_id"] for x in self.find_plates()
1058
+ }
1059
+ # apply post processing
1060
+ if "tenant_id" in res_df.columns:
1061
+ res_df.drop(["tenant_id"], axis=1, inplace=True)
1062
+
1063
+ if "user_group" in res_df.columns:
1064
+ spaces = {x["id"]: x["usergroup_name"] for x in self.get_spaces()}
1065
+ res_df["space"] = res_df["user_group"].apply(
1066
+ lambda x: spaces.get(x, "General")
1067
+ )
1068
+ res_df.drop(["user_group"], axis=1, inplace=True)
1069
+ if "plate_id" in res_df.columns:
1070
+ res_df["plate_uuid"] = res_df["plate_id"]
1071
+ res_df["plate_id"] = res_df["plate_uuid"].apply(
1072
+ lambda x: plate_uuid_to_id.get(x, None)
1073
+ )
1074
+
1075
+ custom_columns = [
1076
+ x["field_name"] for x in self._get_sample_custom_fields()
1077
+ ]
1078
+ res_df = res_df[
1079
+ [
1080
+ x
1081
+ for x in res_df.columns
1082
+ if not x.startswith("custom_") or x in custom_columns
1083
+ ]
1084
+ ]
1085
+
1086
+ if res_df.empty and as_df:
1087
+ return pd.DataFrame(columns=SAMPLE_COLUMNS)
1088
+ return res_df.to_dict(orient="records") if not as_df else res_df
1089
+
1090
+ def _filter_samples_metadata(
1091
+ self,
1092
+ project_id: str,
1093
+ filter: str,
1094
+ sample_ids: list = None,
1095
+ ):
1096
+ """
1097
+ ****************
1098
+ [UNEXPOSED METHOD CALL]
1099
+ ****************
1100
+ Get samples given a filter and project_id.
1101
+
1102
+ Parameters
1103
+ ----------
1104
+ project_id : str
1105
+ The project id.
1106
+ filter : str
1107
+ The filter to be applied. Acceptable values are 'control' or 'sample'.
1108
+ sample_ids : list, optional
1109
+ List of user provided sample ids
572
1110
 
573
1111
  Returns
574
1112
  -------
@@ -579,7 +1117,7 @@ class SeerSDK:
579
1117
  -------
580
1118
  >>> from core import SeerSDK
581
1119
  >>> seer_sdk = SeerSDK()
582
- >>> seer_sdk._get_samples_filter("FILTER", "PROJECT_ID")
1120
+ >>> seer_sdk._filter_samples_metadata("FILTER", "PROJECT_ID")
583
1121
  >>> {
584
1122
  "samples": [
585
1123
  {
@@ -600,7 +1138,7 @@ class SeerSDK:
600
1138
  "Invalid filter. Please choose between 'control' or 'sample'."
601
1139
  )
602
1140
 
603
- df = self.get_samples(project_id=project_id, as_df=True)
1141
+ df = self.find_samples(project_id=project_id, as_df=True)
604
1142
 
605
1143
  if filter == "control":
606
1144
  df = df[~df["control"].isna()]
@@ -619,7 +1157,7 @@ class SeerSDK:
619
1157
  """
620
1158
  URL = f"{self._auth.url}api/v1/samplefields"
621
1159
 
622
- with self._get_auth_session() as s:
1160
+ with self._get_auth_session("getcustomcolumns") as s:
623
1161
 
624
1162
  fields = s.get(URL)
625
1163
 
@@ -633,6 +1171,11 @@ class SeerSDK:
633
1171
  del entry["tenant_id"]
634
1172
  return res
635
1173
 
1174
+ @deprecation.deprecated(
1175
+ deprecated_in="1.1.0",
1176
+ removed_in="2.0.0",
1177
+ details="This method is deprecated and will be removed in a future release. Use `find_msruns` instead.",
1178
+ )
636
1179
  def get_msruns(self, sample_ids: list, as_df: bool = False):
637
1180
  """
638
1181
  Fetches MS data files for passed in `sample_ids` (provided they are valid and contain relevant files) for an authenticated user.
@@ -676,7 +1219,74 @@ class SeerSDK:
676
1219
  res = []
677
1220
  for sample_id in sample_ids:
678
1221
 
679
- with self._get_auth_session() as s:
1222
+ with self._get_auth_session("getmsdatas") as s:
1223
+
1224
+ msdatas = s.post(URL, json={"sampleId": sample_id})
1225
+
1226
+ if msdatas.status_code != 200 or not msdatas.json()["data"]:
1227
+ raise ValueError(
1228
+ f"Failed to fetch MS data for sample ID={sample_id}."
1229
+ )
1230
+
1231
+ res += [x for x in msdatas.json()["data"]]
1232
+
1233
+ for entry in res:
1234
+ if "tenant_id" in entry:
1235
+ del entry["tenant_id"]
1236
+
1237
+ if "raw_file_path" in entry:
1238
+ # Simple lambda function to find the third occurrence of '/' in the raw file path
1239
+ location = lambda s: len(s) - len(s.split("/", 3)[-1])
1240
+ # Slicing the string from the location
1241
+ entry["raw_file_path"] = entry["raw_file_path"][
1242
+ location(entry["raw_file_path"]) :
1243
+ ]
1244
+ return res if not as_df else dict_to_df(res)
1245
+
1246
+ def find_msruns(self, sample_ids: list, as_df: bool = False):
1247
+ """
1248
+ Fetches MS data files for passed in `sample_ids` (provided they are valid and contain relevant files) for an authenticated user.
1249
+
1250
+ The function returns a dict containing DataFrame objects if the `df` flag is passed in as True, otherwise a nested dict object is returned instead.
1251
+
1252
+ Parameters
1253
+ ----------
1254
+ sample_ids : list
1255
+ List of unique sample IDs.
1256
+ as_df: bool
1257
+ whether the result should be converted to a DataFrame, defaulted to False.
1258
+
1259
+ Returns
1260
+ -------
1261
+ res: list[dict] or DataFrame
1262
+ List/DataFrame of plate objects for the authenticated user.
1263
+
1264
+ Examples
1265
+ -------
1266
+ >>> from seer_pas_sdk import SeerSDK
1267
+ >>> seer_sdk = SeerSDK()
1268
+ >>> sample_ids = ["812139c0-15e0-11ee-bdf1-bbaa73585acf", "803e05b0-15e0-11ee-bdf1-bbaa73585acf"]
1269
+
1270
+ >>> seer_sdk.find_msruns(sample_ids)
1271
+ >>> [
1272
+ {"id": "SAMPLE_ID_1_HERE" ... },
1273
+ {"id": "SAMPLE_ID_2_HERE" ... }
1274
+ ]
1275
+
1276
+ >>> seer_sdk.find_msruns(sample_ids, as_df=True)
1277
+ >>> id ... gradient
1278
+ 0 81c6a180-15e0-11ee-bdf1-bbaa73585acf ... None
1279
+ 1 816a9ed0-15e0-11ee-bdf1-bbaa73585acf ... None
1280
+
1281
+ [2 rows x 26 columns]
1282
+ """
1283
+
1284
+ URL = f"{self._auth.url}api/v1/msdatas/items"
1285
+
1286
+ res = []
1287
+ for sample_id in sample_ids:
1288
+
1289
+ with self._get_auth_session("findmsdatas") as s:
680
1290
 
681
1291
  msdatas = s.post(URL, json={"sampleId": sample_id})
682
1292
 
@@ -687,6 +1297,7 @@ class SeerSDK:
687
1297
 
688
1298
  res += [x for x in msdatas.json()["data"]]
689
1299
 
1300
+ spaces = {x["id"]: x["usergroup_name"] for x in self.get_spaces()}
690
1301
  for entry in res:
691
1302
  if "tenant_id" in entry:
692
1303
  del entry["tenant_id"]
@@ -698,8 +1309,19 @@ class SeerSDK:
698
1309
  entry["raw_file_path"] = entry["raw_file_path"][
699
1310
  location(entry["raw_file_path"]) :
700
1311
  ]
1312
+ if "user_group" in entry:
1313
+ entry["space"] = spaces.get(entry["user_group"], "General")
1314
+ del entry["user_group"]
1315
+
1316
+ if not res and as_df:
1317
+ return pd.DataFrame(columns=MSRUN_COLUMNS)
701
1318
  return res if not as_df else dict_to_df(res)
702
1319
 
1320
+ @deprecation.deprecated(
1321
+ deprecated_in="1.1.0",
1322
+ removed_in="2.0.0",
1323
+ details="This method is deprecated and will be removed in a future release. Use `find_analysis_protocols` instead.",
1324
+ )
703
1325
  def get_analysis_protocols(
704
1326
  self,
705
1327
  analysis_protocol_name: str = None,
@@ -724,80 +1346,515 @@ class SeerSDK:
724
1346
  protocols: list[dict]
725
1347
  List of analysis protocol objects for the authenticated user.
726
1348
 
727
- Examples
728
- -------
729
- >>> from seer_pas_sdk import SeerSDK
730
- >>> seer_sdk = SeerSDK()
731
- >>> seer_sdk.get_analysis_protocols()
732
- >>> [
733
- { "id": ..., "analysis_protocol_name": ... },
734
- { "id": ..., "analysis_protocol_name": ... },
735
- ...
736
- ]
1349
+ Examples
1350
+ -------
1351
+ >>> from seer_pas_sdk import SeerSDK
1352
+ >>> seer_sdk = SeerSDK()
1353
+ >>> seer_sdk.get_analysis_protocols()
1354
+ >>> [
1355
+ { "id": ..., "analysis_protocol_name": ... },
1356
+ { "id": ..., "analysis_protocol_name": ... },
1357
+ ...
1358
+ ]
1359
+
1360
+ >>> seer_sdk.get_analysis_protocols(name="YOUR_ANALYSIS_PROTOCOL_NAME_HERE")
1361
+ >>> [{ "id": ..., "analysis_protocol_name": ... }]
1362
+
1363
+ >>> seer_sdk.get_analysis_protocols(id="YOUR_ANALYSIS_PROTOCOL_ID_HERE")
1364
+ >>> [{ "id": ..., "analysis_protocol_name": ... }]
1365
+
1366
+ >>> seer_sdk.get_analysis_protocols(id="YOUR_ANALYSIS_PROTOCOL_ID_HERE", name="YOUR_ANALYSIS_PROTOCOL_NAME_HERE")
1367
+
1368
+ >>> [{ "id": ..., "analysis_protocol_name": ... }] # in this case the id would supersede the inputted name.
1369
+ """
1370
+
1371
+ URL = (
1372
+ f"{self._auth.url}api/v1/analysisProtocols"
1373
+ if not analysis_protocol_id
1374
+ else f"{self._auth.url}api/v1/analysisProtocols/{analysis_protocol_id}"
1375
+ )
1376
+ res = []
1377
+ params = {"all": "true"}
1378
+
1379
+ if analysis_protocol_name:
1380
+ params.update(
1381
+ {
1382
+ "searchFields": "analysis_protocol_name,offering_name",
1383
+ "searchItem": analysis_protocol_name,
1384
+ }
1385
+ )
1386
+
1387
+ with self._get_auth_session("getanalysisprotocols") as s:
1388
+
1389
+ protocols = s.get(URL, params=params)
1390
+ if protocols.status_code != 200:
1391
+ raise ValueError(
1392
+ "Invalid request. Please check your parameters."
1393
+ )
1394
+ if analysis_protocol_id:
1395
+ res = [protocols.json()]
1396
+ else:
1397
+ res = protocols.json()["data"]
1398
+
1399
+ for entry in range(len(res)):
1400
+ if "tenant_id" in res[entry]:
1401
+ del res[entry]["tenant_id"]
1402
+
1403
+ if "can_edit" in res[entry]:
1404
+ del res[entry]["can_edit"]
1405
+
1406
+ if "can_delete" in res[entry]:
1407
+ del res[entry]["can_delete"]
1408
+
1409
+ if "scope" in res[entry]:
1410
+ del res[entry]["scope"]
1411
+
1412
+ if "parameter_file_path" in res[entry]:
1413
+ # Simple lambda function to find the third occurrence of '/' in the raw file path
1414
+ location = lambda s: len(s) - len(s.split("/", 3)[-1])
1415
+ # Slicing the string from the location
1416
+ res[entry]["parameter_file_path"] = res[entry][
1417
+ "parameter_file_path"
1418
+ ][location(res[entry]["parameter_file_path"]) :]
1419
+
1420
+ return res if not as_df else dict_to_df(res)
1421
+
1422
+ def get_analysis_protocol(
1423
+ self,
1424
+ analysis_protocol_id: str = None,
1425
+ analysis_protocol_name: str = None,
1426
+ ):
1427
+ """
1428
+ Fetches an analysis protocol.
1429
+
1430
+ Args:
1431
+ analysis_protocol_id (str, optional): id of the analysis protocol to be fetched. Defaults to None.
1432
+ analysis_protocol_name (str, optional): name of the analysis protocol to be fetched. Defaults to None.
1433
+ Returns:
1434
+ dict: Analysis protocol object
1435
+ Examples
1436
+ -------
1437
+ >>> from seer_pas_sdk import SeerSDK
1438
+ >>> seer_sdk = SeerSDK()
1439
+ >>> seer_sdk.get_analysis_protocol(analysis_protocol_id="YOUR_ANALYSIS_PROTOCOL_ID_HERE")
1440
+ >>> { "id": ..., "analysis_protocol_name": ... }
1441
+ >>> seer_sdk.get_analysis_protocol(analysis_protocol_name="YOUR_ANALYSIS_PROTOCOL_NAME_HERE")
1442
+ >>> { "id": ..., "analysis_protocol_name": ... }
1443
+ """
1444
+
1445
+ if not (bool(analysis_protocol_id) ^ bool(analysis_protocol_name)):
1446
+ raise ValueError(
1447
+ "You must provide either analysis_protocol_id or analysis_protocol_name, but not both."
1448
+ )
1449
+
1450
+ if analysis_protocol_id:
1451
+ URL = f"{self._auth.url}api/v1/analysisProtocols/{analysis_protocol_id}"
1452
+ with self._get_auth_session("getanalysisprotocol") as s:
1453
+ protocols = s.get(URL)
1454
+ if protocols.status_code != 200:
1455
+ raise ValueError(
1456
+ "Invalid request. Please check your parameters."
1457
+ )
1458
+ res = protocols.json()
1459
+ spaces = {
1460
+ x["id"]: x["usergroup_name"] for x in self.get_spaces()
1461
+ }
1462
+ if "tenant_id" in res:
1463
+ del res["tenant_id"]
1464
+ if "user_group" in res:
1465
+ res["space"] = spaces.get(res["user_group"], "General")
1466
+ del res["user_group"]
1467
+ try:
1468
+ res["fasta"] = ",".join(
1469
+ self._get_analysis_protocol_fasta_filenames(
1470
+ analysis_protocol_id=res["id"],
1471
+ analysis_protocol_engine=res["analysis_engine"],
1472
+ )
1473
+ )
1474
+ except:
1475
+ res["fasta"] = ""
1476
+ return res
1477
+ else:
1478
+ res = self.find_analysis_protocols(
1479
+ analysis_protocol_name=analysis_protocol_name
1480
+ )
1481
+ if not res:
1482
+ raise ValueError(
1483
+ f"No analysis protocol found with name '{analysis_protocol_name}'."
1484
+ )
1485
+ elif len(res) > 1:
1486
+ raise ValueError(
1487
+ f"Multiple analysis protocols found with name '{analysis_protocol_name}'. Please specify an analysis_protocol_id."
1488
+ )
1489
+ else:
1490
+ return res[0]
1491
+
1492
+ def find_analysis_protocols(
1493
+ self,
1494
+ analysis_protocol_name: str = None,
1495
+ analysis_protocol_id: str = None,
1496
+ as_df: bool = False,
1497
+ ):
1498
+ """
1499
+ Fetches a list of analysis protocols for the authenticated user. If no `analysis_protocol_id` is provided, returns all analysis protocols for the authenticated user. If `analysis_protocol_name` (and no `analysis_protocol_id`) is provided, returns the analysis protocol with the given name, provided it exists.
1500
+
1501
+ Parameters
1502
+ ----------
1503
+ analysis_protocol_id : str, optional
1504
+ ID of the analysis protocol to be fetched, defaulted to None.
1505
+
1506
+ analysis_protocol_name : str, optional
1507
+ Name of the analysis protocol to be fetched, defaulted to None.
1508
+
1509
+ as_df : bool, optional
1510
+ whether the result should be converted to a DataFrame, defaulted to False.
1511
+ Returns
1512
+ -------
1513
+ protocols: list[dict]
1514
+ List of analysis protocol objects for the authenticated user.
1515
+
1516
+ Examples
1517
+ -------
1518
+ >>> from seer_pas_sdk import SeerSDK
1519
+ >>> seer_sdk = SeerSDK()
1520
+ >>> seer_sdk.find_analysis_protocols()
1521
+ >>> [
1522
+ { "id": ..., "analysis_protocol_name": ... },
1523
+ { "id": ..., "analysis_protocol_name": ... },
1524
+ ...
1525
+ ]
1526
+
1527
+ >>> seer_sdk.find_analysis_protocols(name="YOUR_ANALYSIS_PROTOCOL_NAME_HERE")
1528
+ >>> [{ "id": ..., "analysis_protocol_name": ... }]
1529
+
1530
+ >>> seer_sdk.find_analysis_protocols(id="YOUR_ANALYSIS_PROTOCOL_ID_HERE")
1531
+ >>> [{ "id": ..., "analysis_protocol_name": ... }]
1532
+
1533
+ >>> seer_sdk.find_analysis_protocols(id="YOUR_ANALYSIS_PROTOCOL_ID_HERE", name="YOUR_ANALYSIS_PROTOCOL_NAME_HERE")
1534
+
1535
+ >>> [{ "id": ..., "analysis_protocol_name": ... }] # in this case the id would supersede the inputted name.
1536
+ """
1537
+
1538
+ URL = f"{self._auth.url}api/v1/analysisProtocols"
1539
+ res = []
1540
+ params = {"all": "true"}
1541
+
1542
+ if analysis_protocol_name:
1543
+ params.update(
1544
+ {
1545
+ "searchFields": "analysis_protocol_name,offering_name",
1546
+ "searchItem": analysis_protocol_name,
1547
+ }
1548
+ )
1549
+ elif analysis_protocol_id:
1550
+ params.update(
1551
+ {"searchFields": "id", "searchItem": analysis_protocol_id}
1552
+ )
1553
+
1554
+ with self._get_auth_session("findanalysisprotocols") as s:
1555
+
1556
+ protocols = s.get(URL, params=params)
1557
+ if protocols.status_code != 200:
1558
+ raise ValueError(
1559
+ "Invalid request. Please check your parameters."
1560
+ )
1561
+ res = protocols.json()["data"]
1562
+
1563
+ spaces = {x["id"]: x["usergroup_name"] for x in self.get_spaces()}
1564
+ for entry in range(len(res)):
1565
+ if "tenant_id" in res[entry]:
1566
+ del res[entry]["tenant_id"]
1567
+
1568
+ if "can_edit" in res[entry]:
1569
+ del res[entry]["can_edit"]
1570
+
1571
+ if "can_delete" in res[entry]:
1572
+ del res[entry]["can_delete"]
1573
+
1574
+ if "scope" in res[entry]:
1575
+ del res[entry]["scope"]
1576
+
1577
+ if "parameter_file_path" in res[entry]:
1578
+ # Simple lambda function to find the third occurrence of '/' in the raw file path
1579
+ location = lambda s: len(s) - len(s.split("/", 3)[-1])
1580
+ # Slicing the string from the location
1581
+ res[entry]["parameter_file_path"] = res[entry][
1582
+ "parameter_file_path"
1583
+ ][location(res[entry]["parameter_file_path"]) :]
1584
+ if "user_group" in res[entry]:
1585
+ res[entry]["space"] = spaces.get(
1586
+ res[entry]["user_group"], "General"
1587
+ )
1588
+ del res[entry]["user_group"]
1589
+
1590
+ try:
1591
+ res[entry]["fasta"] = ",".join(
1592
+ self._get_analysis_protocol_fasta_filenames(
1593
+ analysis_protocol_id=res[entry]["id"],
1594
+ analysis_protocol_engine=res[entry].get(
1595
+ "analysis_engine", None
1596
+ ),
1597
+ )
1598
+ )
1599
+ except:
1600
+ res[entry]["fasta"] = ""
1601
+
1602
+ if not res and as_df:
1603
+ return pd.DataFrame(columns=ANALYSIS_PROTOCOL_COLUMNS)
1604
+ return res if not as_df else dict_to_df(res)
1605
+
1606
+ @deprecation.deprecated(
1607
+ deprecated_in="1.1.0",
1608
+ removed_in="2.0.0",
1609
+ details="This method is deprecated and will be removed in a future release. Use `find_analyses` instead.",
1610
+ )
1611
+ def get_analyses(
1612
+ self,
1613
+ analysis_id: str = None,
1614
+ folder_id: str = None,
1615
+ show_folders: bool = True,
1616
+ analysis_only: bool = True,
1617
+ project_id: str = None,
1618
+ plate_name: str = None,
1619
+ as_df=False,
1620
+ **kwargs,
1621
+ ):
1622
+ """
1623
+ Returns a list of analyses objects for the authenticated user. If no id is provided, returns all analyses for the authenticated user.
1624
+ Search parameters may be passed in as keyword arguments to filter the results. Acceptable values are 'analysis_name', 'folder_name', 'description', 'notes', or 'number_msdatafile'.
1625
+ Only search on a single field is supported.
1626
+
1627
+ Parameters
1628
+ ----------
1629
+ analysis_id : str, optional
1630
+ ID of the analysis to be fetched, defaulted to None.
1631
+
1632
+ folder_id : str, optional
1633
+ ID of the folder to be fetched, defaulted to None.
1634
+
1635
+ show_folders : bool, optional
1636
+ Mark True if folder contents are to be returned in the response, i.e. recursive search, defaulted to True.
1637
+ Will be disabled if an analysis id is provided.
1638
+
1639
+ analysis_only : bool, optional
1640
+ Mark True if only analyses objects are to be returned in the response, defaulted to True.
1641
+ If marked false, folder objects will also be included in the response.
1642
+
1643
+ project_id : str, optional
1644
+ ID of the project to be fetched, defaulted to None.
1645
+
1646
+ plate_name : str, optional
1647
+ Name of the plate to be fetched, defaulted to None.
1648
+
1649
+ as_df : bool, optional
1650
+ whether the result should be converted to a DataFrame, defaulted to False.
1651
+
1652
+ **kwargs : dict, optional
1653
+ Search keyword parameters to be passed in. Acceptable values are 'analysis_name', 'folder_name', 'analysis_protocol_name', 'description', 'notes', or 'number_msdatafile'.
1654
+
1655
+ Returns
1656
+ -------
1657
+ analyses: list[dict]
1658
+ Contains a list of analyses objects for the authenticated user.
1659
+
1660
+ Examples
1661
+ -------
1662
+ >>> from seer_pas_sdk import SeerSDK
1663
+ >>> seer_sdk = SeerSDK()
1664
+ >>> seer_sdk.get_analyses()
1665
+ >>> [
1666
+ {id: "YOUR_ANALYSIS_ID_HERE", ...},
1667
+ {id: "YOUR_ANALYSIS_ID_HERE", ...},
1668
+ {id: "YOUR_ANALYSIS_ID_HERE", ...}
1669
+ ]
1670
+
1671
+ >>> seer_sdk.get_analyses("YOUR_ANALYSIS_ID_HERE")
1672
+ >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
1673
+
1674
+ >>> seer_sdk.get_analyses(folder_name="YOUR_FOLDER_NAME_HERE")
1675
+ >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
1676
+
1677
+ >>> seer_sdk.get_analyses(analysis_name="YOUR_ANALYSIS")
1678
+ >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
1679
+
1680
+ >>> seer_sdk.get_analyses(description="YOUR_DESCRIPTION")
1681
+ >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
1682
+ """
1683
+
1684
+ URL = f"{self._auth.url}api/v1/analyses"
1685
+ res = []
1686
+
1687
+ search_field = None
1688
+ search_item = None
1689
+ if kwargs:
1690
+ if len(kwargs.keys()) > 1:
1691
+ raise ValueError("Please include only one search parameter.")
1692
+ search_field = list(kwargs.keys())[0]
1693
+ search_item = kwargs[search_field]
1694
+
1695
+ if not search_item:
1696
+ raise ValueError(
1697
+ f"Please provide a non null value for {search_field}"
1698
+ )
1699
+
1700
+ if search_field and search_field not in [
1701
+ "analysis_name",
1702
+ "folder_name",
1703
+ "analysis_protocol_name",
1704
+ "description",
1705
+ "notes",
1706
+ "number_msdatafile",
1707
+ ]:
1708
+ raise ValueError(
1709
+ "Invalid search field. Please choose between 'analysis_name', 'folder_name', 'analysis_protocol_name', 'description', 'notes', or 'number_msdatafile'."
1710
+ )
1711
+
1712
+ with self._get_auth_session("getanalyses") as s:
737
1713
 
738
- >>> seer_sdk.get_analysis_protocols(name="YOUR_ANALYSIS_PROTOCOL_NAME_HERE")
739
- >>> [{ "id": ..., "analysis_protocol_name": ... }]
1714
+ params = {"all": "true"}
1715
+ if folder_id:
1716
+ params["folder"] = folder_id
740
1717
 
741
- >>> seer_sdk.get_analysis_protocols(id="YOUR_ANALYSIS_PROTOCOL_ID_HERE")
742
- >>> [{ "id": ..., "analysis_protocol_name": ... }]
1718
+ if search_field:
1719
+ params["searchFields"] = search_field
1720
+ params["searchItem"] = search_item
1721
+ del params["all"]
743
1722
 
744
- >>> seer_sdk.get_analysis_protocols(id="YOUR_ANALYSIS_PROTOCOL_ID_HERE", name="YOUR_ANALYSIS_PROTOCOL_NAME_HERE")
1723
+ if search_field == "folder_name":
1724
+ params["searchFields"] = "analysis_name"
745
1725
 
746
- >>> [{ "id": ..., "analysis_protocol_name": ... }] # in this case the id would supersede the inputted name.
747
- """
1726
+ if project_id:
1727
+ params["projectId"] = project_id
748
1728
 
749
- URL = (
750
- f"{self._auth.url}api/v1/analysisProtocols"
751
- if not analysis_protocol_id
752
- else f"{self._auth.url}api/v1/analysisProtocols/{analysis_protocol_id}"
753
- )
754
- res = []
755
- params = {"all": "true"}
1729
+ if plate_name:
1730
+ params["plateName"] = plate_name
756
1731
 
757
- if analysis_protocol_name:
758
- params.update(
759
- {
760
- "searchFields": "analysis_protocol_name,offering_name",
761
- "searchItem": analysis_protocol_name,
762
- }
1732
+ analyses = s.get(
1733
+ f"{URL}/{analysis_id}" if analysis_id else URL, params=params
763
1734
  )
764
1735
 
765
- with self._get_auth_session() as s:
766
-
767
- protocols = s.get(URL, params=params)
768
- if protocols.status_code != 200:
1736
+ if analyses.status_code != 200:
769
1737
  raise ValueError(
770
1738
  "Invalid request. Please check your parameters."
771
1739
  )
772
- if analysis_protocol_id:
773
- res = [protocols.json()]
1740
+ if not analysis_id:
1741
+ res = analyses.json()["data"]
1742
+
774
1743
  else:
775
- res = protocols.json()["data"]
1744
+ res = [analyses.json()["analysis"]]
776
1745
 
1746
+ folders = []
777
1747
  for entry in range(len(res)):
778
1748
  if "tenant_id" in res[entry]:
779
1749
  del res[entry]["tenant_id"]
780
1750
 
781
- if "can_edit" in res[entry]:
782
- del res[entry]["can_edit"]
783
-
784
- if "can_delete" in res[entry]:
785
- del res[entry]["can_delete"]
786
-
787
- if "scope" in res[entry]:
788
- del res[entry]["scope"]
789
-
790
1751
  if "parameter_file_path" in res[entry]:
791
1752
  # Simple lambda function to find the third occurrence of '/' in the raw file path
792
1753
  location = lambda s: len(s) - len(s.split("/", 3)[-1])
1754
+
793
1755
  # Slicing the string from the location
794
1756
  res[entry]["parameter_file_path"] = res[entry][
795
1757
  "parameter_file_path"
796
1758
  ][location(res[entry]["parameter_file_path"]) :]
797
1759
 
1760
+ if (
1761
+ show_folders
1762
+ and not analysis_id
1763
+ and res[entry]["is_folder"]
1764
+ ):
1765
+ folders.append(res[entry]["id"])
1766
+
1767
+ # recursive solution to get analyses in folders
1768
+ for folder in folders:
1769
+ res += self.get_analyses(folder_id=folder)
1770
+
1771
+ if analysis_only:
1772
+ res = [
1773
+ analysis for analysis in res if not analysis["is_folder"]
1774
+ ]
798
1775
  return res if not as_df else dict_to_df(res)
799
1776
 
800
- def get_analyses(
1777
+ def get_analysis(
1778
+ self,
1779
+ analysis_id: str = None,
1780
+ analysis_name: str = None,
1781
+ ):
1782
+ """Fetches an analysis.
1783
+
1784
+ Args:
1785
+ analysis_id (str, optional): id of the analysis to be fetched. Defaults to None.
1786
+ analysis_name (str, optional): name of the analysis to be fetched. Defaults to None.
1787
+
1788
+ Returns:
1789
+ analysis : dict
1790
+ Analysis object
1791
+ Examples
1792
+ -------
1793
+ >>> from seer_pas_sdk import SeerSDK
1794
+ >>> seer_sdk = SeerSDK()
1795
+ >>> seer_sdk.get_analysis(analysis_id="YOUR_ANALYSIS_ID_HERE")
1796
+ >>> { "id": ..., "analysis_name": ... }
1797
+
1798
+ """
1799
+ if not (bool(analysis_id) ^ bool(analysis_name)):
1800
+ raise ValueError(
1801
+ "You must provide either analysis_id or analysis_name, but not both."
1802
+ )
1803
+
1804
+ if analysis_id:
1805
+ URL = f"{self._auth.url}api/v1/analyses/{analysis_id}"
1806
+ with self._get_auth_session("getanalysis") as s:
1807
+ analysis = s.get(URL)
1808
+ if analysis.status_code != 200:
1809
+ raise ValueError(
1810
+ "Invalid request. Please check your parameters."
1811
+ )
1812
+ res = analysis.json()["analysis"]
1813
+ spaces = {
1814
+ x["id"]: x["usergroup_name"] for x in self.get_spaces()
1815
+ }
1816
+ if "tenant_id" in res:
1817
+ del res["tenant_id"]
1818
+ if "user_group" in res:
1819
+ res["space"] = spaces.get(res["user_group"], "General")
1820
+ del res["user_group"]
1821
+ if not res.get("is_folder") and res.get(
1822
+ "analysis_protocol_id"
1823
+ ):
1824
+ analysis_protocol = self.get_analysis_protocol(
1825
+ analysis_protocol_id=res.get("analysis_protocol_id")
1826
+ )
1827
+ try:
1828
+ res["fasta"] = ",".join(
1829
+ self._get_analysis_protocol_fasta_filenames(
1830
+ analysis_protocol_id=res.get(
1831
+ "analysis_protocol_id"
1832
+ ),
1833
+ analysis_protocol_engine=analysis_protocol.get(
1834
+ "analysis_engine", None
1835
+ ),
1836
+ )
1837
+ )
1838
+ except Exception as e:
1839
+ print("Warning: Could not fetch fasta files.")
1840
+ res["fasta"] = None
1841
+ else:
1842
+ res["fasta"] = None
1843
+ return res
1844
+ else:
1845
+ res = self.find_analyses(analysis_name=analysis_name)
1846
+ if not res:
1847
+ raise ValueError(
1848
+ f"No analysis found with name '{analysis_name}'."
1849
+ )
1850
+ elif len(res) > 1:
1851
+ raise ValueError(
1852
+ f"Multiple analyses found with name '{analysis_name}'. Please specify an analysis_id."
1853
+ )
1854
+ else:
1855
+ return res[0]
1856
+
1857
+ def find_analyses(
801
1858
  self,
802
1859
  analysis_id: str = None,
803
1860
  folder_id: str = None,
@@ -850,23 +1907,23 @@ class SeerSDK:
850
1907
  -------
851
1908
  >>> from seer_pas_sdk import SeerSDK
852
1909
  >>> seer_sdk = SeerSDK()
853
- >>> seer_sdk.get_analyses()
1910
+ >>> seer_sdk.find_analyses()
854
1911
  >>> [
855
1912
  {id: "YOUR_ANALYSIS_ID_HERE", ...},
856
1913
  {id: "YOUR_ANALYSIS_ID_HERE", ...},
857
1914
  {id: "YOUR_ANALYSIS_ID_HERE", ...}
858
1915
  ]
859
1916
 
860
- >>> seer_sdk.get_analyses("YOUR_ANALYSIS_ID_HERE")
1917
+ >>> seer_sdk.find_analyses("YOUR_ANALYSIS_ID_HERE")
861
1918
  >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
862
1919
 
863
- >>> seer_sdk.get_analyses(folder_name="YOUR_FOLDER_NAME_HERE")
1920
+ >>> seer_sdk.find_analyses(folder_name="YOUR_FOLDER_NAME_HERE")
864
1921
  >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
865
1922
 
866
- >>> seer_sdk.get_analyses(analysis_name="YOUR_ANALYSIS")
1923
+ >>> seer_sdk.find_analyses(analysis_name="YOUR_ANALYSIS")
867
1924
  >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
868
1925
 
869
- >>> seer_sdk.get_analyses(description="YOUR_DESCRIPTION")
1926
+ >>> seer_sdk.find_analyses(description="YOUR_DESCRIPTION")
870
1927
  >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
871
1928
  """
872
1929
 
@@ -898,7 +1955,13 @@ class SeerSDK:
898
1955
  "Invalid search field. Please choose between 'analysis_name', 'folder_name', 'analysis_protocol_name', 'description', 'notes', or 'number_msdatafile'."
899
1956
  )
900
1957
 
901
- with self._get_auth_session() as s:
1958
+ if analysis_id:
1959
+ try:
1960
+ return [self.get_analysis(analysis_id=analysis_id)]
1961
+ except:
1962
+ return []
1963
+
1964
+ with self._get_auth_session("findanalyses") as s:
902
1965
 
903
1966
  params = {"all": "true"}
904
1967
  if folder_id:
@@ -918,30 +1981,25 @@ class SeerSDK:
918
1981
  if plate_name:
919
1982
  params["plateName"] = plate_name
920
1983
 
921
- analyses = s.get(
922
- f"{URL}/{analysis_id}" if analysis_id else URL, params=params
923
- )
1984
+ analyses = s.get(URL, params=params)
924
1985
 
925
1986
  if analyses.status_code != 200:
926
1987
  raise ValueError(
927
1988
  "Invalid request. Please check your parameters."
928
1989
  )
929
- if not analysis_id:
930
- res = analyses.json()["data"]
931
-
932
- else:
933
- res = [analyses.json()["analysis"]]
1990
+ res = analyses.json()["data"]
934
1991
 
935
1992
  folders = []
1993
+ spaces = {x["id"]: x["usergroup_name"] for x in self.get_spaces()}
1994
+ protocol_to_engine_map = dict()
936
1995
  for entry in range(len(res)):
937
1996
  if "tenant_id" in res[entry]:
938
1997
  del res[entry]["tenant_id"]
939
1998
 
940
1999
  if "parameter_file_path" in res[entry]:
941
- # Simple lambda function to find the third occurrence of '/' in the raw file path
2000
+ # Trim parameter file path to user visible attributes
942
2001
  location = lambda s: len(s) - len(s.split("/", 3)[-1])
943
2002
 
944
- # Slicing the string from the location
945
2003
  res[entry]["parameter_file_path"] = res[entry][
946
2004
  "parameter_file_path"
947
2005
  ][location(res[entry]["parameter_file_path"]) :]
@@ -953,17 +2011,65 @@ class SeerSDK:
953
2011
  ):
954
2012
  folders.append(res[entry]["id"])
955
2013
 
2014
+ if "user_group" in res[entry]:
2015
+ res[entry]["space"] = spaces.get(
2016
+ res[entry]["user_group"], "General"
2017
+ )
2018
+ del res[entry]["user_group"]
2019
+
2020
+ if (not res[entry].get("is_folder")) and res[entry].get(
2021
+ "analysis_protocol_id"
2022
+ ):
2023
+ if (
2024
+ res[entry]["analysis_protocol_id"]
2025
+ in protocol_to_engine_map
2026
+ ):
2027
+ analysis_protocol_engine = protocol_to_engine_map[
2028
+ res[entry]["analysis_protocol_id"]
2029
+ ]
2030
+ else:
2031
+ try:
2032
+ analysis_protocol = self.get_analysis_protocol(
2033
+ analysis_protocol_id=res[entry].get(
2034
+ "analysis_protocol_id"
2035
+ )
2036
+ )
2037
+ analysis_protocol_engine = analysis_protocol.get(
2038
+ "analysis_engine", None
2039
+ )
2040
+ protocol_to_engine_map[
2041
+ res[entry]["analysis_protocol_id"]
2042
+ ] = analysis_protocol_engine
2043
+ except:
2044
+ analysis_protocol_engine = None
2045
+ try:
2046
+ res[entry]["fasta"] = ",".join(
2047
+ self._get_analysis_protocol_fasta_filenames(
2048
+ res[entry]["analysis_protocol_id"],
2049
+ analysis_protocol_engine=analysis_protocol_engine,
2050
+ )
2051
+ )
2052
+ except:
2053
+ print(
2054
+ f"Warning: Could not fetch fasta files for analysis {res[entry].get('analysis_name')}."
2055
+ )
2056
+ res[entry]["fasta"] = None
2057
+ else:
2058
+ res[entry]["fasta"] = None
2059
+
956
2060
  # recursive solution to get analyses in folders
957
2061
  for folder in folders:
958
- res += self.get_analyses(folder_id=folder)
2062
+ res += self.find_analyses(folder_id=folder)
959
2063
 
960
2064
  if analysis_only:
961
2065
  res = [
962
2066
  analysis for analysis in res if not analysis["is_folder"]
963
2067
  ]
2068
+ if not res and as_df:
2069
+ return pd.DataFrame(columns=ANALYSIS_COLUMNS)
964
2070
  return res if not as_df else dict_to_df(res)
965
2071
 
966
- @deprecation.deprecated(deprecated_in="0.3.0", removed_in="1.0.0")
2072
+ @deprecation.deprecated(deprecated_in="0.3.0", removed_in="2.0.0")
967
2073
  def get_analysis_result_protein_data(
968
2074
  self, analysis_id: str, link: bool = False, pg: str = None
969
2075
  ):
@@ -981,7 +2087,7 @@ class SeerSDK:
981
2087
  Protein group ID to filter dataframe results. Defaults to None.
982
2088
 
983
2089
  """
984
- with self._get_auth_session() as s:
2090
+ with self._get_auth_session("getanalysisresultprotein") as s:
985
2091
  URL = f"{self._auth.url}api/v1/data"
986
2092
  response = s.get(
987
2093
  f"{URL}/protein?analysisId={analysis_id}&retry=false"
@@ -1013,16 +2119,18 @@ class SeerSDK:
1013
2119
  else:
1014
2120
  if not pg:
1015
2121
  return {
1016
- "protein_np": url_to_df(protein_data["npLink"]["url"]),
1017
- "protein_panel": url_to_df(
2122
+ "protein_np": download_df(
2123
+ protein_data["npLink"]["url"]
2124
+ ),
2125
+ "protein_panel": download_df(
1018
2126
  protein_data["panelLink"]["url"]
1019
2127
  ),
1020
2128
  }
1021
2129
  else:
1022
- protein_np = url_to_df(
2130
+ protein_np = download_df(
1023
2131
  protein_data["npLink"]["url"]
1024
2132
  ).query(f"`Protein Group` == '{pg}'")
1025
- protein_panel = url_to_df(
2133
+ protein_panel = download_df(
1026
2134
  protein_data["panelLink"]["url"]
1027
2135
  ).query(f"`Protein Group` == '{pg}'")
1028
2136
 
@@ -1036,7 +2144,7 @@ class SeerSDK:
1036
2144
  "protein_panel": protein_panel,
1037
2145
  }
1038
2146
 
1039
- @deprecation.deprecated(deprecated_in="0.3.0", removed_in="1.0.0")
2147
+ @deprecation.deprecated(deprecated_in="0.3.0", removed_in="2.0.0")
1040
2148
  def get_analysis_result_peptide_data(
1041
2149
  self, analysis_id: str, link: bool = False, peptide: str = None
1042
2150
  ):
@@ -1057,7 +2165,7 @@ class SeerSDK:
1057
2165
 
1058
2166
  """
1059
2167
 
1060
- with self._get_auth_session() as s:
2168
+ with self._get_auth_session("getanalysisresultpeptide") as s:
1061
2169
  URL = f"{self._auth.url}api/v1/data"
1062
2170
  response = s.get(
1063
2171
  f"{URL}/peptide?analysisId={analysis_id}&retry=false"
@@ -1089,16 +2197,18 @@ class SeerSDK:
1089
2197
  else:
1090
2198
  if not peptide:
1091
2199
  return {
1092
- "peptide_np": url_to_df(peptide_data["npLink"]["url"]),
1093
- "peptide_panel": url_to_df(
2200
+ "peptide_np": download_df(
2201
+ peptide_data["npLink"]["url"]
2202
+ ),
2203
+ "peptide_panel": download_df(
1094
2204
  peptide_data["panelLink"]["url"]
1095
2205
  ),
1096
2206
  }
1097
2207
  else:
1098
- peptide_np = url_to_df(
2208
+ peptide_np = download_df(
1099
2209
  peptide_data["npLink"]["url"]
1100
2210
  ).query(f"Peptide == '{peptide}'")
1101
- peptide_panel = url_to_df(
2211
+ peptide_panel = download_df(
1102
2212
  peptide_data["panelLink"]["url"]
1103
2213
  ).query(f"Peptide == '{peptide}'")
1104
2214
 
@@ -1121,7 +2231,7 @@ class SeerSDK:
1121
2231
  analysis_id : str
1122
2232
  ID of the analysis for which the data is to be fetched.
1123
2233
  """
1124
- with self._get_auth_session() as s:
2234
+ with self._get_auth_session("getsearchresultprotein") as s:
1125
2235
  URL = f"{self._auth.url}api/v1/data"
1126
2236
  response = s.get(
1127
2237
  f"{URL}/protein?analysisId={analysis_id}&retry=false"
@@ -1167,7 +2277,7 @@ class SeerSDK:
1167
2277
 
1168
2278
  """
1169
2279
 
1170
- with self._get_auth_session() as s:
2280
+ with self._get_auth_session("getsearchresultpeptide") as s:
1171
2281
  URL = f"{self._auth.url}api/v1/data"
1172
2282
  response = s.get(
1173
2283
  f"{URL}/peptide?analysisId={analysis_id}&retry=false"
@@ -1220,7 +2330,7 @@ class SeerSDK:
1220
2330
  List of files associated with the analysis.
1221
2331
  """
1222
2332
  try:
1223
- analysis_metadata = self.get_analyses(analysis_id)[0]
2333
+ analysis_metadata = self.find_analyses(analysis_id)[0]
1224
2334
  except (IndexError, ServerError):
1225
2335
  raise ValueError("Invalid analysis ID.")
1226
2336
  except:
@@ -1233,7 +2343,7 @@ class SeerSDK:
1233
2343
  if folder:
1234
2344
  params["folderKey"] = folder
1235
2345
 
1236
- with self._get_auth_session() as s:
2346
+ with self._get_auth_session("listsearchresultfiles") as s:
1237
2347
  response = s.get(
1238
2348
  f"{self._auth.url}api/v2/analysisResultFiles/{analysis_id}",
1239
2349
  params=params,
@@ -1284,47 +2394,54 @@ class SeerSDK:
1284
2394
 
1285
2395
  if analyte_type not in ["protein", "peptide", "precursor"]:
1286
2396
  raise ValueError(
1287
- "Invalid data type. Please choose between 'protein', 'peptide', or 'precursor'."
2397
+ "Unknown analyte_type = '{analyte_type}'. Supported types are 'protein', 'peptide', or 'precursor'."
1288
2398
  )
1289
2399
 
1290
2400
  if rollup not in ["np", "panel"]:
1291
2401
  raise ValueError(
1292
- "Invalid file. Please choose between 'np', 'panel'."
2402
+ "Unknown rollup = '{rollup}'. Supported rollup types are 'np' and 'panel'."
1293
2403
  )
1294
2404
 
1295
2405
  if analyte_type == "precursor" and rollup == "panel":
1296
2406
  raise ValueError(
1297
- "Precursor data is not available for panel rollup, please select np rollup."
2407
+ "Precursor data is not available for panel rollup, use rollup = 'np' to get precursor data."
1298
2408
  )
1299
2409
 
2410
+ # cast Sample Name to str to ensure correct interpretation of sample names like '001'
2411
+ dtype = {"Sample Name": str}
2412
+
1300
2413
  if analyte_type == "protein":
1301
2414
  if rollup == "np":
1302
- return url_to_df(
2415
+ return download_df(
1303
2416
  self._get_search_result_protein_data(analysis_id)[
1304
2417
  "npLink"
1305
- ]["url"]
2418
+ ]["url"],
2419
+ dtype=dtype,
1306
2420
  )
1307
2421
  elif rollup == "panel":
1308
- return url_to_df(
2422
+ return download_df(
1309
2423
  self._get_search_result_protein_data(analysis_id)[
1310
2424
  "panelLink"
1311
- ]["url"]
2425
+ ]["url"],
2426
+ dtype=dtype,
1312
2427
  )
1313
2428
  elif analyte_type == "peptide":
1314
2429
  if rollup == "np":
1315
- return url_to_df(
2430
+ return download_df(
1316
2431
  self._get_search_result_peptide_data(analysis_id)[
1317
2432
  "npLink"
1318
- ]["url"]
2433
+ ]["url"],
2434
+ dtype=dtype,
1319
2435
  )
1320
2436
  elif rollup == "panel":
1321
- return url_to_df(
2437
+ return download_df(
1322
2438
  self._get_search_result_peptide_data(analysis_id)[
1323
2439
  "panelLink"
1324
- ]["url"]
2440
+ ]["url"],
2441
+ dtype=dtype,
1325
2442
  )
1326
2443
  else:
1327
- return url_to_df(
2444
+ return download_df(
1328
2445
  self.get_search_result_file_url(
1329
2446
  analysis_id, filename="report.tsv"
1330
2447
  )["url"]
@@ -1344,18 +2461,21 @@ class SeerSDK:
1344
2461
  filename : str
1345
2462
  Name of the file to be fetched. Files can be case insensitive and without file extensions.
1346
2463
 
1347
- download_path : str
2464
+ download_path : str, optional
1348
2465
  String flag denoting where the user wants the files downloaded. Can be local or absolute as long as the path is valid.
1349
2466
 
1350
2467
  Returns
1351
2468
  -------
1352
- None
1353
- Downloads the file to the specified path.
2469
+ str
2470
+ Path to the downloaded file.
1354
2471
  """
1355
2472
 
1356
2473
  if not download_path:
1357
2474
  download_path = os.getcwd()
1358
2475
 
2476
+ if download_path.endswith("/"):
2477
+ download_path = download_path.rstrip("/")
2478
+
1359
2479
  if not analysis_id:
1360
2480
  raise ValueError("Analysis ID cannot be empty.")
1361
2481
 
@@ -1363,7 +2483,6 @@ class SeerSDK:
1363
2483
  raise ValueError(
1364
2484
  "Please specify a valid folder path as download path."
1365
2485
  )
1366
-
1367
2486
  file = self.get_search_result_file_url(analysis_id, filename)
1368
2487
  file_url = file["url"]
1369
2488
  filename = file["filename"]
@@ -1389,15 +2508,10 @@ class SeerSDK:
1389
2508
  )
1390
2509
  break
1391
2510
  except:
1392
- filename = filename.split("/")
1393
- name += "/" + "/".join(
1394
- [filename[i] for i in range(len(filename) - 1)]
1395
- )
1396
- filename = filename[-1]
1397
- if not os.path.isdir(f"{name}/{filename}"):
1398
- os.makedirs(f"{name}/")
1399
- print(f"File {filename} downloaded successfully to {download_path}.")
1400
- return
2511
+ dirname = f"{download_path}/{os.path.dirname(filename)}"
2512
+ if not os.path.isdir(f"{dirname}"):
2513
+ os.makedirs(f"{dirname}")
2514
+ return f"{download_path}/{filename}"
1401
2515
 
1402
2516
  def get_search_result_file_url(self, analysis_id: str, filename: str):
1403
2517
  """
@@ -1416,12 +2530,19 @@ class SeerSDK:
1416
2530
  file_url: dict[str, str]
1417
2531
  Dictionary containing the 'url' and 'filename' of the file.
1418
2532
  """
2533
+ pas_dirname = os.path.dirname(filename)
1419
2534
  if "." in filename:
1420
2535
  filename = ".".join(filename.split(".")[:-1])
1421
2536
  filename = filename.casefold()
1422
2537
 
2538
+ if pas_dirname:
2539
+ analysis_result_files = self.list_search_result_files(
2540
+ analysis_id, folder=pas_dirname
2541
+ )
2542
+ else:
2543
+ analysis_result_files = self.list_search_result_files(analysis_id)
2544
+
1423
2545
  # Allow user to pass in filenames without an extension.
1424
- analysis_result_files = self.list_search_result_files(analysis_id)
1425
2546
  analysis_result_files_prefix_mapper = {
1426
2547
  (".".join(x.split(".")[:-1])).casefold(): x
1427
2548
  for x in analysis_result_files
@@ -1433,10 +2554,10 @@ class SeerSDK:
1433
2554
  f"Filename {filename} not among the available analysis result files. Please use SeerSDK.list_search_result_files('{analysis_id}') to see available files for this analysis."
1434
2555
  )
1435
2556
 
1436
- analysis_metadata = self.get_analyses(analysis_id)[0]
2557
+ analysis_metadata = self.get_analysis(analysis_id)
1437
2558
  if analysis_metadata.get("status") in ["Failed", None]:
1438
2559
  raise ValueError("Cannot generate links for failed searches.")
1439
- with self._get_auth_session() as s:
2560
+ with self._get_auth_session("getsearchresultfileurl") as s:
1440
2561
  file_url = s.post(
1441
2562
  f"{self._auth.url}api/v1/analysisResultFiles/getUrl",
1442
2563
  json={
@@ -1452,7 +2573,7 @@ class SeerSDK:
1452
2573
  response["filename"] = filename
1453
2574
  return response
1454
2575
 
1455
- @deprecation.deprecated(deprecated_in="0.3.0", removed_in="1.0.0")
2576
+ @deprecation.deprecated(deprecated_in="0.3.0", removed_in="2.0.0")
1456
2577
  def get_analysis_result_files(
1457
2578
  self,
1458
2579
  analysis_id: str,
@@ -1578,7 +2699,7 @@ class SeerSDK:
1578
2699
  continue
1579
2700
 
1580
2701
  links = {
1581
- k: url_to_df(v, is_tsv=k.endswith(".tsv"))
2702
+ k: download_df(v, is_tsv=k.endswith(".tsv"))
1582
2703
  for k, v in links.items()
1583
2704
  }
1584
2705
  if download_path:
@@ -1595,7 +2716,7 @@ class SeerSDK:
1595
2716
 
1596
2717
  return links
1597
2718
 
1598
- @deprecation.deprecated(deprecated_in="0.3.0", removed_in="1.0.0")
2719
+ @deprecation.deprecated(deprecated_in="0.3.0", removed_in="2.0.0")
1599
2720
  def get_analysis_result(
1600
2721
  self,
1601
2722
  analysis_id: str,
@@ -1660,17 +2781,17 @@ class SeerSDK:
1660
2781
  analysis_id, link=True
1661
2782
  )
1662
2783
  links = {
1663
- "peptide_np": url_to_df(peptide_data["npLink"]["url"]),
1664
- "peptide_panel": url_to_df(peptide_data["panelLink"]["url"]),
1665
- "protein_np": url_to_df(protein_data["npLink"]["url"]),
1666
- "protein_panel": url_to_df(protein_data["panelLink"]["url"]),
2784
+ "peptide_np": download_df(peptide_data["npLink"]["url"]),
2785
+ "peptide_panel": download_df(peptide_data["panelLink"]["url"]),
2786
+ "protein_np": download_df(protein_data["npLink"]["url"]),
2787
+ "protein_panel": download_df(protein_data["panelLink"]["url"]),
1667
2788
  }
1668
2789
 
1669
2790
  if diann_report:
1670
2791
  diann_report_url = self._get_search_result_file_url(
1671
2792
  analysis_id, "report.tsv"
1672
2793
  )
1673
- links["diann_report"] = url_to_df(diann_report_url["url"])
2794
+ links["diann_report"] = download_df(diann_report_url["url"])
1674
2795
 
1675
2796
  if download_path:
1676
2797
  name = f"{download_path}/downloads/{analysis_id}"
@@ -1723,7 +2844,7 @@ class SeerSDK:
1723
2844
  raise ValueError("Analysis id cannot be empty.")
1724
2845
 
1725
2846
  try:
1726
- res = self.get_analyses(analysis_id)
2847
+ res = self.find_analyses(analysis_id)
1727
2848
  except ValueError:
1728
2849
  return ValueError("Analysis not found. Your ID could be incorrect")
1729
2850
 
@@ -1757,12 +2878,12 @@ class SeerSDK:
1757
2878
  )
1758
2879
 
1759
2880
  if not analysis_id and analysis_name:
1760
- analysis_id = self.get_analyses(analysis_name=analysis_name)[0][
2881
+ analysis_id = self.find_analyses(analysis_name=analysis_name)[0][
1761
2882
  "id"
1762
2883
  ]
1763
2884
 
1764
2885
  URL = self._auth.url + "api/v2/groupanalysis/protein"
1765
- with self._get_auth_session() as s:
2886
+ with self._get_auth_session("getproteinresults") as s:
1766
2887
  res = s.post(
1767
2888
  URL, json={"analysisId": analysis_id, "grouping": grouping}
1768
2889
  )
@@ -1826,12 +2947,12 @@ class SeerSDK:
1826
2947
  )
1827
2948
 
1828
2949
  if not analysis_id and analysis_name:
1829
- analysis_id = self.get_analyses(analysis_name=analysis_name)[0][
2950
+ analysis_id = self.find_analyses(analysis_name=analysis_name)[0][
1830
2951
  "id"
1831
2952
  ]
1832
2953
 
1833
2954
  URL = self._auth.url + "api/v2/groupanalysis/peptide"
1834
- with self._get_auth_session() as s:
2955
+ with self._get_auth_session("getpeptideresults") as s:
1835
2956
  res = s.post(
1836
2957
  URL, json={"analysisId": analysis_id, "grouping": grouping}
1837
2958
  )
@@ -1901,7 +3022,7 @@ class SeerSDK:
1901
3022
  if not space
1902
3023
  else f"{self._auth.url}api/v1/msdataindex/filesinfolder?folder={folder}&userGroupId={space}"
1903
3024
  )
1904
- with self._get_auth_session() as s:
3025
+ with self._get_auth_session("listmsdatafiles") as s:
1905
3026
 
1906
3027
  files = s.get(URL)
1907
3028
 
@@ -1912,7 +3033,7 @@ class SeerSDK:
1912
3033
  return files.json()["filesList"]
1913
3034
 
1914
3035
  def download_ms_data_files(
1915
- self, paths: _List[str], download_path: str, space: str = None
3036
+ self, paths: _List[str], download_path: str = "", space: str = None
1916
3037
  ):
1917
3038
  """
1918
3039
  Downloads all MS data files for paths passed in the params to the specified download path.
@@ -1921,15 +3042,14 @@ class SeerSDK:
1921
3042
  ----------
1922
3043
  paths : list[str]
1923
3044
  List of paths to download.
1924
- download_path : str
1925
- Path to download the files to.
3045
+ download_path : str, optional
3046
+ Path to download the files to. Defaults to current working directory.
1926
3047
  space : str, optional
1927
3048
  ID of the user group to which the files belongs, defaulted to None.
1928
3049
 
1929
3050
  Returns
1930
3051
  -------
1931
- message: dict[str, str]
1932
- Contains the 'message' whether the files were downloaded or not.
3052
+ list[str] : the list of paths to the downloaded files.
1933
3053
  """
1934
3054
 
1935
3055
  urls = []
@@ -1957,8 +3077,7 @@ class SeerSDK:
1957
3077
  tenant_id = self._auth.active_tenant_id
1958
3078
 
1959
3079
  for path in paths:
1960
- with self._get_auth_session() as s:
1961
-
3080
+ with self._get_auth_session("getmsdataindexurl") as s:
1962
3081
  download_url = s.post(
1963
3082
  URL,
1964
3083
  json={
@@ -1972,6 +3091,8 @@ class SeerSDK:
1972
3091
  "Could not download file. Please check if the backend is running."
1973
3092
  )
1974
3093
  urls.append(download_url.text)
3094
+
3095
+ downloads = []
1975
3096
  for i in range(len(urls)):
1976
3097
  filename = paths[i].split("/")[-1]
1977
3098
  url = urls[i]
@@ -1996,6 +3117,7 @@ class SeerSDK:
1996
3117
  reporthook=download_hook(t),
1997
3118
  data=None,
1998
3119
  )
3120
+ downloads.append(f"{name}/{filename}")
1999
3121
  break
2000
3122
  except:
2001
3123
  filename = filename.split("/")
@@ -2013,7 +3135,7 @@ class SeerSDK:
2013
3135
 
2014
3136
  print(f"Finished downloading {filename}\n")
2015
3137
 
2016
- return {"message": f"Files downloaded successfully to '{name}'"}
3138
+ return downloads
2017
3139
 
2018
3140
  def get_group_analysis(
2019
3141
  self, analysis_id, group_analysis_id=None, **kwargs
@@ -2062,7 +3184,7 @@ class SeerSDK:
2062
3184
  URL = f"{URL}/{group_analysis_id}"
2063
3185
  params["id"] = group_analysis_id
2064
3186
 
2065
- with self._get_auth_session() as s:
3187
+ with self._get_auth_session("getgroupanalyses") as s:
2066
3188
  response = s.get(URL, params=params)
2067
3189
  if response.status_code != 200:
2068
3190
  raise ServerError(
@@ -2141,7 +3263,7 @@ class SeerSDK:
2141
3263
  }
2142
3264
 
2143
3265
  # Pre-GA data call
2144
- with self._get_auth_session() as s:
3266
+ with self._get_auth_session("getanalysisresultprotein") as s:
2145
3267
 
2146
3268
  protein_pre_data = s.post(
2147
3269
  url=f"{URL}api/v2/groupanalysis/protein",
@@ -2156,7 +3278,7 @@ class SeerSDK:
2156
3278
 
2157
3279
  res["pre"]["protein"] = protein_pre_data
2158
3280
 
2159
- with self._get_auth_session() as s:
3281
+ with self._get_auth_session("getanalysisresultpeptide") as s:
2160
3282
 
2161
3283
  peptide_pre_data = s.post(
2162
3284
  url=f"{URL}api/v2/groupanalysis/peptide",
@@ -2172,7 +3294,7 @@ class SeerSDK:
2172
3294
  res["pre"]["peptide"] = peptide_pre_data
2173
3295
 
2174
3296
  # Post-GA data call
2175
- with self._get_auth_session() as s:
3297
+ with self._get_auth_session("getgroupanalysispost") as s:
2176
3298
  if group_analysis_id:
2177
3299
  get_saved_result = self.get_group_analysis(
2178
3300
  analysis_id=analysis_id,
@@ -2255,7 +3377,7 @@ class SeerSDK:
2255
3377
  'proteinId', 'intensity', 'sampleName', 'sampleId', 'condition','gene'
2256
3378
  """
2257
3379
 
2258
- with self._get_auth_session() as s:
3380
+ with self._get_auth_session("getgroupanalysisraw") as s:
2259
3381
 
2260
3382
  # API call 1 - get volcano plot data for filtered results and gene mapping
2261
3383
  builder = self.get_volcano_plot_data(
@@ -2411,7 +3533,7 @@ class SeerSDK:
2411
3533
 
2412
3534
  URL = f"{self._auth.url}api/v1/analysisqcpca"
2413
3535
 
2414
- with self._get_auth_session() as s:
3536
+ with self._get_auth_session("getanalysispca") as s:
2415
3537
  json = {
2416
3538
  "analysisIds": ",".join(analysis_ids),
2417
3539
  "type": type,
@@ -2561,7 +3683,7 @@ class SeerSDK:
2561
3683
 
2562
3684
  URL = f"{self._auth.url}api/v1/analysishcluster"
2563
3685
 
2564
- with self._get_auth_session() as s:
3686
+ with self._get_auth_session("getanalysishclust") as s:
2565
3687
  json = {
2566
3688
  "analysisIds": ",".join(analysis_ids),
2567
3689
  }
@@ -2617,7 +3739,7 @@ class SeerSDK:
2617
3739
 
2618
3740
  URL = f"{self._auth.url}api/v1/groupanalysis/stringdb"
2619
3741
 
2620
- with self._get_auth_session() as s:
3742
+ with self._get_auth_session("getppinetwork") as s:
2621
3743
  json = {
2622
3744
  "significantPGs": ",".join(significant_pgs),
2623
3745
  }
@@ -2698,7 +3820,7 @@ class SeerSDK:
2698
3820
  significantPGs=",".join(significant_pgs),
2699
3821
  )
2700
3822
 
2701
- with self._get_auth_session() as s:
3823
+ with self._get_auth_session("getanalysisclusterheatmap") as s:
2702
3824
  URL = f"{self._auth.url}api/v2/clusterheatmap"
2703
3825
  response = s.post(URL, json=payload)
2704
3826
  if response.status_code != 200:
@@ -2737,7 +3859,7 @@ class SeerSDK:
2737
3859
  if not significant_pgs:
2738
3860
  raise ValueError("Significant pgs cannot be empty.")
2739
3861
 
2740
- with self._get_auth_session() as s:
3862
+ with self._get_auth_session("getanalysisenrichmentplot") as s:
2741
3863
  json = {
2742
3864
  "analysisId": analysis_id,
2743
3865
  "significantPGs": significant_pgs,
@@ -2827,12 +3949,12 @@ class SeerSDK:
2827
3949
  if analysis_id:
2828
3950
  rows = [{"id": analysis_id}]
2829
3951
  else:
2830
- rows = self.get_analyses(analysis_name=analysis_name)
3952
+ rows = self.find_analyses(analysis_name=analysis_name)
2831
3953
 
2832
3954
  resp = []
2833
3955
  for row in rows:
2834
3956
  URL = f"{self._auth.url}api/v1/analyses/samples/{row['id']}"
2835
- with self._get_auth_session() as s:
3957
+ with self._get_auth_session("getanalysissamples") as s:
2836
3958
  samples = s.get(URL)
2837
3959
  try:
2838
3960
  samples.raise_for_status()
@@ -2841,15 +3963,15 @@ class SeerSDK:
2841
3963
  except:
2842
3964
  continue
2843
3965
 
2844
- if not resp:
2845
- raise ServerError(
2846
- f"Could not retrieve samples for analysis {analysis_id or analysis_name}."
2847
- )
2848
-
2849
3966
  resp = pd.DataFrame(resp)
2850
3967
  resp.drop_duplicates(subset=["id"], inplace=True)
2851
3968
  return resp if as_df else resp.to_dict(orient="records")
2852
3969
 
3970
+ @deprecation.deprecated(
3971
+ deprecated_in="1.1.0",
3972
+ removed_in="2.0.0",
3973
+ details="get_analysis_protocol_fasta is deprecated. Use download_analysis_protocol_fasta instead.",
3974
+ )
2853
3975
  def get_analysis_protocol_fasta(self, analysis_id, download_path=None):
2854
3976
  if not analysis_id:
2855
3977
  raise ValueError("Analysis ID cannot be empty.")
@@ -2858,14 +3980,14 @@ class SeerSDK:
2858
3980
  download_path = os.getcwd()
2859
3981
 
2860
3982
  try:
2861
- analysis_protocol_id = self.get_analyses(analysis_id)[0][
3983
+ analysis_protocol_id = self.find_analyses(analysis_id)[0][
2862
3984
  "analysis_protocol_id"
2863
3985
  ]
2864
3986
  except (IndexError, KeyError):
2865
3987
  raise ValueError(f"Could not parse server response.")
2866
3988
 
2867
3989
  try:
2868
- analysis_protocol_engine = self.get_analysis_protocols(
3990
+ analysis_protocol_engine = self.find_analysis_protocols(
2869
3991
  analysis_protocol_id=analysis_protocol_id
2870
3992
  )[0]["analysis_engine"]
2871
3993
  except (IndexError, KeyError):
@@ -2887,7 +4009,7 @@ class SeerSDK:
2887
4009
  f"Analysis protocol engine {analysis_protocol_engine} not supported for fasta download."
2888
4010
  )
2889
4011
 
2890
- with self._get_auth_session() as s:
4012
+ with self._get_auth_session("getanalysisprotocolparameters") as s:
2891
4013
  response = s.get(URL)
2892
4014
  if response.status_code != 200:
2893
4015
  raise ServerError("Request failed.")
@@ -2904,7 +4026,7 @@ class SeerSDK:
2904
4026
 
2905
4027
  URL = f"{self._auth.url}api/v1/analysisProtocolFiles/getUrl"
2906
4028
  for file in fasta_filenames:
2907
- with self._get_auth_session() as s:
4029
+ with self._get_auth_session("getanalysisprotocolfilesurl") as s:
2908
4030
  response = s.post(URL, json={"filepath": file})
2909
4031
  if response.status_code != 200:
2910
4032
  raise ServerError("Request failed.")
@@ -2935,3 +4057,172 @@ class SeerSDK:
2935
4057
  os.makedirs(f"{download_path}")
2936
4058
 
2937
4059
  print(f"Downloaded file to {download_path}/{file}")
4060
+
4061
+ def _get_analysis_protocol_fasta_filenames(
4062
+ self, analysis_protocol_id: str, analysis_protocol_engine: str
4063
+ ):
4064
+ """
4065
+ Helper function - Get the fasta file name(s) associated with a given analysis protocol and engine.
4066
+ Args:
4067
+ analysis_protocol_id (str): ID of the analysis protocol.
4068
+ analysis_protocol_engine (str): Analysis engine of the analysis protocol.
4069
+ Returns:
4070
+ list[str]: A list of fasta file names associated with the analysis protocol.
4071
+ """
4072
+ analysis_protocol_engine = analysis_protocol_engine.lower()
4073
+ if analysis_protocol_engine == "diann":
4074
+ URL = f"{self._auth.url}api/v1/analysisProtocols/editableParameters/diann/{analysis_protocol_id}"
4075
+ elif analysis_protocol_engine == "encyclopedia":
4076
+ URL = f"{self._auth.url}api/v1/analysisProtocols/editableParameters/dia/{analysis_protocol_id}"
4077
+ elif analysis_protocol_engine == "msfragger":
4078
+ URL = f"{self._auth.url}api/v1/analysisProtocols/editableParameters/msfragger/{analysis_protocol_id}"
4079
+ elif analysis_protocol_engine == "proteogenomics":
4080
+ URL = f"{self._auth.url}api/v1/analysisProtocols/editableParameters/proteogenomics/{analysis_protocol_id}"
4081
+ elif analysis_protocol_engine == "maxquant":
4082
+ URL = f"{self._auth.url}api/v1/analysisProtocols/editableParameters/{analysis_protocol_id}"
4083
+ else:
4084
+ raise ValueError(
4085
+ f"Analysis protocol engine {analysis_protocol_engine} not supported for fasta download."
4086
+ )
4087
+
4088
+ with self._get_auth_session("getanalysisprotocolparameters") as s:
4089
+ response = s.get(URL)
4090
+ if response.status_code != 200:
4091
+ raise ServerError("Failed to retrieve analysis protocol data.")
4092
+ response = response.json()
4093
+ if isinstance(response, dict):
4094
+ response = response["editableParameters"]
4095
+ fasta_filenames = (
4096
+ np.array(
4097
+ [
4098
+ x["Value"]
4099
+ for x in response
4100
+ if x["Key"]
4101
+ in ["fasta", "fastaFilePath", "referencegenome"]
4102
+ ]
4103
+ )
4104
+ .flatten()
4105
+ .tolist()
4106
+ )
4107
+ if not fasta_filenames:
4108
+ raise ServerError("No fasta file name returned from server.")
4109
+ return fasta_filenames
4110
+
4111
+ def get_analysis_protocol_fasta_link(
4112
+ self, analysis_protocol_id=None, analysis_id=None, analysis_name=None
4113
+ ):
4114
+ """Get the download link(s) for the fasta file(s) associated with a given analysis protocol.
4115
+ Args:
4116
+ analysis_protocol_id (str,optional): ID of the analysis protocol. Defaults to None.
4117
+ analysis_id (str, optional): ID of the analysis. Defaults to None.
4118
+ analysis_name (str, optional): Name of the analysis. Defaults to None.
4119
+
4120
+ Returns:
4121
+ list[dict]: A list of dictionaries containing the 'filename' and the 'url' to download the fasta file.
4122
+ """
4123
+ if analysis_name and (not analysis_id):
4124
+ analyses = self.find_analyses(analysis_name=analysis_name)
4125
+ if len(analyses) > 1:
4126
+ raise ValueError(
4127
+ f"Multiple analyses found with name {analysis_name}. Please provide an analysis ID instead."
4128
+ )
4129
+ elif len(analyses) == 0:
4130
+ raise ValueError(
4131
+ f"No analyses found with name {analysis_name}."
4132
+ )
4133
+ else:
4134
+ analysis_id = analyses[0]["id"]
4135
+
4136
+ if not (bool(analysis_protocol_id) ^ bool(analysis_id)):
4137
+ raise ValueError(
4138
+ "Please provide either an analysis ID or an analysis protocol ID."
4139
+ )
4140
+
4141
+ if not analysis_protocol_id:
4142
+ try:
4143
+ analysis_protocol_id = self.get_analysis(
4144
+ analysis_id=analysis_id
4145
+ )["analysis_protocol_id"]
4146
+ except KeyError:
4147
+ raise ValueError(f"Could not parse server response.")
4148
+
4149
+ engine = self.get_analysis_protocol(
4150
+ analysis_protocol_id=analysis_protocol_id
4151
+ ).get("analysis_engine", None)
4152
+ fasta_filenames = self._get_analysis_protocol_fasta_filenames(
4153
+ analysis_protocol_id=analysis_protocol_id,
4154
+ analysis_protocol_engine=engine,
4155
+ )
4156
+ URL = f"{self._auth.url}api/v1/analysisProtocolFiles/getUrl"
4157
+ links = []
4158
+ for file in fasta_filenames:
4159
+ with self._get_auth_session("getanalysisprotocolfilesurl") as s:
4160
+ filename = os.path.basename(file)
4161
+ response = s.post(URL, json={"filepath": file})
4162
+ if response.status_code != 200:
4163
+ print(
4164
+ f"ERROR: Could not retrieve download link for {filename}."
4165
+ )
4166
+ continue
4167
+ url = response.json()["url"]
4168
+ links.append({"filename": filename, "url": url})
4169
+ return links
4170
+
4171
+ def download_analysis_protocol_fasta(
4172
+ self,
4173
+ analysis_protocol_id=None,
4174
+ analysis_id=None,
4175
+ analysis_name=None,
4176
+ download_path=None,
4177
+ ):
4178
+ """Download the fasta file(s) associated with a given analysis protocol.
4179
+
4180
+ Args:
4181
+ analysis_protocol_id (str,optional): ID of the analysis protocol. Defaults to None.
4182
+ analysis_id (str, optional): ID of the analysis. Defaults to None.
4183
+ analysis_name (str, optional): Name of the analysis. Defaults to None.
4184
+ download_path (str, optional): Path to download the fasta file to. Defaults to current working directory.
4185
+
4186
+ Returns:
4187
+ list[str] : The path to the downloaded fasta file(s).
4188
+ """
4189
+
4190
+ links = [
4191
+ (x["filename"], x["url"])
4192
+ for x in self.get_analysis_protocol_fasta_link(
4193
+ analysis_protocol_id=analysis_protocol_id,
4194
+ analysis_id=analysis_id,
4195
+ analysis_name=analysis_name,
4196
+ )
4197
+ ]
4198
+ if not download_path:
4199
+ download_path = os.getcwd()
4200
+
4201
+ downloads = []
4202
+ for filename, url in links:
4203
+ print(f"Downloading {filename}")
4204
+ for _ in range(2):
4205
+ try:
4206
+ with tqdm(
4207
+ unit="B",
4208
+ unit_scale=True,
4209
+ unit_divisor=1024,
4210
+ miniters=1,
4211
+ desc=f"Progress",
4212
+ ) as t:
4213
+ ssl._create_default_https_context = (
4214
+ ssl._create_unverified_context
4215
+ )
4216
+ urllib.request.urlretrieve(
4217
+ url,
4218
+ f"{download_path}/{filename}",
4219
+ reporthook=download_hook(t),
4220
+ data=None,
4221
+ )
4222
+ break
4223
+ except:
4224
+ if not os.path.isdir(f"{download_path}"):
4225
+ os.makedirs(f"{download_path}")
4226
+
4227
+ downloads.append(f"{download_path}/{filename}")
4228
+ return downloads