seer-pas-sdk 0.2.1__py3-none-any.whl → 0.3.0__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,5 +1,6 @@
1
1
  from tqdm import tqdm
2
2
 
3
+ import deprecation
3
4
  import os
4
5
  import jwt
5
6
  import requests
@@ -70,14 +71,14 @@ class SeerSDK:
70
71
 
71
72
  return sess
72
73
 
73
- def get_user_tenant_metadata(self, index=True):
74
+ def get_user_tenant(self, index=True):
74
75
  """
75
76
  Fetches the tenant metadata for the authenticated user.
76
77
 
77
78
  Returns
78
79
  -------
79
- response : dict
80
- A dictionary containing the tenant metadata for the authenticated user.
80
+ response : list[dict]
81
+ A list of tenant objects pertaining to the user.
81
82
  """
82
83
  with self._get_auth_session() as s:
83
84
  response = s.get(f"{self._auth.url}api/v1/usertenants")
@@ -89,7 +90,13 @@ class SeerSDK:
89
90
 
90
91
  response = response.json()
91
92
  if index:
92
- return {x["institution"]: x for x in response}
93
+ mapper = dict()
94
+ for x in response:
95
+ if x["institution"] not in mapper:
96
+ mapper[x["institution"]] = [x]
97
+ else:
98
+ mapper[x["institution"]].append(x)
99
+ return mapper
93
100
  else:
94
101
  return response
95
102
 
@@ -104,10 +111,10 @@ class SeerSDK:
104
111
 
105
112
  Returns
106
113
  -------
107
- tenants : dict
114
+ tenants : dict[str, str]
108
115
  A dictionary containing the institution names and tenant ids for the authenticated user.
109
116
  """
110
- tenants = self.get_user_tenant_metadata()
117
+ tenants = self.get_user_tenant()
111
118
  if reverse:
112
119
  return {x["tenantId"]: x["institution"] for x in tenants.values()}
113
120
  else:
@@ -127,13 +134,15 @@ class SeerSDK:
127
134
  tenant_id: str
128
135
  Returns the value of the active tenant id after the operation.
129
136
  """
130
- map = self.get_user_tenant_metadata()
131
- tenant_ids = [x["tenantId"] for x in map.values()]
137
+ map = self.get_user_tenant()
138
+ tenant_id_match = [
139
+ y for x in map.values() for y in x if y["tenantId"] == identifier
140
+ ]
132
141
  institution_names = map.keys()
133
142
 
134
- if identifier in tenant_ids:
143
+ if tenant_id_match:
135
144
  tenant_id = identifier
136
- row = [x for x in map.values() if x["tenantId"] == tenant_id]
145
+ row = tenant_id_match
137
146
  if row:
138
147
  row = row[0]
139
148
  else:
@@ -141,7 +150,12 @@ class SeerSDK:
141
150
  "Invalid tenant identifier. Tenant was not switched."
142
151
  )
143
152
  elif identifier in institution_names:
144
- row = map[identifier]
153
+ results = map[identifier]
154
+ if len(results) > 1:
155
+ raise ValueError(
156
+ "Multiple tenants found for the given institution name. Please specify a tenant ID."
157
+ )
158
+ row = results[0]
145
159
  tenant_id = row["tenantId"]
146
160
  else:
147
161
  raise ValueError(
@@ -172,10 +186,10 @@ class SeerSDK:
172
186
 
173
187
  Returns
174
188
  -------
175
- tenant: dict
189
+ tenant: dict[str, str]
176
190
  Tenant metadata for the authenticated user containing "institution" and "tenantId" keys.
177
191
  """
178
- tenants = self.get_user_tenant_metadata(index=False)
192
+ tenants = self.get_user_tenant(index=False)
179
193
  row = [
180
194
  x for x in tenants if x["tenantId"] == self._auth.active_tenant_id
181
195
  ]
@@ -211,7 +225,7 @@ class SeerSDK:
211
225
 
212
226
  Returns
213
227
  -------
214
- spaces: list
228
+ spaces: list[dict]
215
229
  List of space objects for the authenticated user.
216
230
 
217
231
  Examples
@@ -237,7 +251,7 @@ class SeerSDK:
237
251
  )
238
252
  return spaces.json()
239
253
 
240
- def get_plate_metadata(self, plate_id: str = None, df: bool = False):
254
+ def get_plates(self, plate_id: str = None, as_df: bool = False):
241
255
  """
242
256
  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.
243
257
 
@@ -245,25 +259,25 @@ class SeerSDK:
245
259
  ----------
246
260
  plate_id : str, optional
247
261
  ID of the plate to be fetched, defaulted to None.
248
- df: bool
249
- Boolean denoting whether the user wants the response back in JSON or a DataFrame object
262
+ as_df: bool
263
+ whether the result should be converted to a DataFrame, defaulted to None.
250
264
 
251
265
  Returns
252
266
  -------
253
- plates: list or DataFrame
267
+ plates: list[dict] or DataFrame
254
268
  List/DataFrame of plate objects for the authenticated user.
255
269
 
256
270
  Examples
257
271
  -------
258
272
  >>> from seer_pas_sdk import SeerSDK
259
273
  >>> seer_sdk = SeerSDK()
260
- >>> seer_sdk.get_plate_metadata()
274
+ >>> seer_sdk.get_plates()
261
275
  >>> [
262
276
  { "id": ... },
263
277
  { "id": ... },
264
278
  ...
265
279
  ]
266
- >>> seer_sdk.get_plate_metadata(df=True)
280
+ >>> seer_sdk.get_plates(as_df=True)
267
281
  >>> id ... user_group
268
282
  0 a7c12190-15da-11ee-bdf1-bbaa73585acf ... None
269
283
  1 8c3b1480-15da-11ee-bdf1-bbaa73585acf ... None
@@ -302,9 +316,9 @@ class SeerSDK:
302
316
  for entry in res:
303
317
  del entry["tenant_id"]
304
318
 
305
- return res if not df else dict_to_df(res)
319
+ return res if not as_df else dict_to_df(res)
306
320
 
307
- def get_project_metadata(self, project_id: str = None, df: bool = False):
321
+ def get_projects(self, project_id: str = None, as_df: bool = False):
308
322
  """
309
323
  Fetches a list of projects for the authenticated user. If no `project_id` is provided, returns all projects for the authenticated user. If `project_id` is provided, returns the project with the given `project_id`, provided it exists.
310
324
 
@@ -312,26 +326,26 @@ class SeerSDK:
312
326
  ----------
313
327
  project_id: str, optional
314
328
  Project ID of the project to be fetched, defaulted to None.
315
- df: bool
316
- Boolean denoting whether the user wants the response back in JSON or a DataFrame object.
329
+ as_df: bool
330
+ whether the result should be converted to a DataFrame, defaulted to False.
317
331
 
318
332
  Returns
319
333
  -------
320
- projects: list or DataFrame
334
+ projects: list[dict] or DataFrame
321
335
  DataFrame or list of project objects for the authenticated user.
322
336
 
323
337
  Examples
324
338
  -------
325
339
  >>> from seer_pas_sdk import SeerSDK
326
340
  >>> seer_sdk = SeerSDK()
327
- >>> seer_sdk.get_project_metadata()
341
+ >>> seer_sdk.get_projects()
328
342
  >>> [
329
343
  { "project_name": ... },
330
344
  { "project_name": ... },
331
345
  ...
332
346
  ]
333
347
 
334
- >>> seer_sdk.get_project_metadata(df=True)
348
+ >>> seer_sdk.get_projects(as_df=True)
335
349
  >>> id ... user_group
336
350
  0 a7c12190-15da-11ee-bdf1-bbaa73585acf ... None
337
351
  1 8c3b1480-15da-11ee-bdf1-bbaa73585acf ... None
@@ -345,7 +359,7 @@ class SeerSDK:
345
359
  938 5b05d440-6610-11ea-96e3-d5a4dab4ebf6 ... None
346
360
  939 9872e3f0-544e-11ea-ad9e-1991e0725494 ... None
347
361
 
348
- >>> seer_sdk.get_project_metadata(id="YOUR_PROJECT_ID_HERE")
362
+ >>> seer_sdk.get_projects(id="YOUR_PROJECT_ID_HERE")
349
363
  >>> [{ "project_name": ... }]
350
364
  """
351
365
 
@@ -379,15 +393,18 @@ class SeerSDK:
379
393
  entry["raw_file_path"] = entry["raw_file_path"][
380
394
  location(entry["raw_file_path"]) :
381
395
  ]
382
- return res if not df else dict_to_df(res)
396
+ return res if not as_df else dict_to_df(res)
383
397
 
384
- def get_samples_metadata(
385
- self, plate_id: str = None, project_id: str = None, df: bool = False
398
+ def get_samples(
399
+ self,
400
+ plate_id: str = None,
401
+ project_id: str = None,
402
+ analysis_id: str = None,
403
+ analysis_name: str = None,
404
+ as_df: bool = False,
386
405
  ):
387
406
  """
388
- Fetches a list of samples for the authenticated user, filtered by `plate_id`. Returns all samples for the plate with the given `plate_id`, provided it exists.
389
-
390
- If both `plate_id` and `project_id` are passed in, only the `plate_id` is validated first.
407
+ Fetches a list of samples for the authenticated user with relation to a specified plate, project, or analysis. If no parameters are provided, returns all samples for the authenticated user. 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.
391
408
 
392
409
  Parameters
393
410
  ----------
@@ -395,12 +412,16 @@ class SeerSDK:
395
412
  ID of the plate for which samples are to be fetched, defaulted to None.
396
413
  project_id : str, optional
397
414
  ID of the project for which samples are to be fetched, defaulted to None.
398
- df: bool
399
- Boolean denoting whether the user wants the response back in JSON or a DataFrame object
415
+ analysis_id : str, optional
416
+ ID of the analysis for which samples are to be fetched, defaulted to None.
417
+ analysis_name : str, optional
418
+ Name of the analysis for which samples are to be fetched, defaulted to None.
419
+ as_df: bool
420
+ whether the result should be converted to a DataFrame, defaulted to False.
400
421
 
401
422
  Returns
402
423
  -------
403
- samples: list or DataFrame
424
+ samples: list[dict] or DataFrame
404
425
  List/DataFrame of samples for the authenticated user.
405
426
 
406
427
  Examples
@@ -408,14 +429,14 @@ class SeerSDK:
408
429
  >>> from seer_pas_sdk import SeerSDK
409
430
  >>> seer_sdk = SeerSDK()
410
431
 
411
- >>> seer_sdk.get_samples_metadata(plate_id="7ec8cad0-15e0-11ee-bdf1-bbaa73585acf")
432
+ >>> seer_sdk.get_samples(plate_id="7ec8cad0-15e0-11ee-bdf1-bbaa73585acf")
412
433
  >>> [
413
434
  { "id": ... },
414
435
  { "id": ... },
415
436
  ...
416
437
  ]
417
438
 
418
- >>> seer_sdk.get_samples_metadata(df=True)
439
+ >>> seer_sdk.get_samples(as_df=True)
419
440
  >>> id ... control
420
441
  0 812139c0-15e0-11ee-bdf1-bbaa73585acf ...
421
442
  1 803e05b0-15e0-11ee-bdf1-bbaa73585acf ... MPE Control
@@ -430,29 +451,40 @@ class SeerSDK:
430
451
  3628 dd607ef0-654c-11ea-8eb2-25a1cfd1163c ... C132
431
452
  """
432
453
 
433
- if not plate_id and not project_id:
434
- raise ValueError("You must pass in plate ID or project ID.")
454
+ # Raise an error if none or more than one of the primary key parameters are passed in.
455
+ if (
456
+ sum(
457
+ [
458
+ True if x else False
459
+ for x in [plate_id, project_id, analysis_id, analysis_name]
460
+ ]
461
+ )
462
+ != 1
463
+ ):
464
+ raise ValueError(
465
+ "You must pass in exactly one of plate_id, project_id, analysis_id, analysis_name."
466
+ )
435
467
 
436
468
  res = []
437
469
  URL = f"{self._auth.url}api/v1/samples"
438
470
  sample_params = {"all": "true"}
439
471
 
440
- with self._get_auth_session() as s:
441
-
442
- if plate_id:
443
- try:
444
- self.get_plate_metadata(plate_id)
445
- except:
446
- raise ValueError("Plate ID is invalid.")
447
- sample_params["plateId"] = plate_id
472
+ if project_id or plate_id:
473
+ with self._get_auth_session() as s:
474
+ if plate_id:
475
+ try:
476
+ self.get_plates(plate_id)
477
+ except:
478
+ raise ValueError("Plate ID is invalid.")
479
+ sample_params["plateId"] = plate_id
448
480
 
449
- elif project_id:
450
- try:
451
- self.get_project_metadata(project_id)
452
- except:
453
- raise ValueError("Project ID is invalid.")
481
+ else:
482
+ try:
483
+ self.get_projects(project_id)
484
+ except:
485
+ raise ValueError("Project ID is invalid.")
454
486
 
455
- sample_params["projectId"] = project_id
487
+ sample_params["projectId"] = project_id
456
488
 
457
489
  samples = s.get(URL, params=sample_params)
458
490
  if samples.status_code != 200:
@@ -460,14 +492,27 @@ class SeerSDK:
460
492
  f"Failed to fetch sample data for plate ID: {plate_id}."
461
493
  )
462
494
  res = samples.json()["data"]
495
+ res_df = dict_to_df(res)
463
496
 
464
- for entry in res:
465
- del entry["tenant_id"]
497
+ # API returns empty strings if not a control, replace with None for filtering purposes
498
+ res_df["control"] = res_df["control"].apply(
499
+ lambda x: x if x else None
500
+ )
501
+ else:
502
+ if analysis_id:
503
+ res_df = self._get_analysis_samples(
504
+ analysis_id=analysis_id, as_df=True
505
+ )
506
+ else:
507
+ res_df = self._get_analysis_samples(
508
+ analysis_name=analysis_name, as_df=True, is_name=True
509
+ )
510
+
511
+ # apply post processing
512
+ res_df.drop(["tenant_id"], axis=1, inplace=True)
466
513
 
467
- # Exclude custom fields that don't belong to the tenant
468
- res_df = dict_to_df(res)
469
514
  custom_columns = [
470
- x["field_name"] for x in self.get_sample_custom_fields()
515
+ x["field_name"] for x in self._get_sample_custom_fields()
471
516
  ]
472
517
  res_df = res_df[
473
518
  [
@@ -477,10 +522,7 @@ class SeerSDK:
477
522
  ]
478
523
  ]
479
524
 
480
- # API returns empty strings if not a control, replace with None for filtering purposes
481
- res_df["control"] = res_df["control"].apply(lambda x: x if x else None)
482
-
483
- return res_df.to_dict(orient="records") if not df else res_df
525
+ return res_df.to_dict(orient="records") if not as_df else res_df
484
526
 
485
527
  def _filter_samples_metadata(
486
528
  self,
@@ -505,7 +547,7 @@ class SeerSDK:
505
547
 
506
548
  Returns
507
549
  -------
508
- res : list
550
+ res : list[str]
509
551
  A list of sample ids
510
552
 
511
553
  Examples
@@ -533,7 +575,7 @@ class SeerSDK:
533
575
  "Invalid filter. Please choose between 'control' or 'sample'."
534
576
  )
535
577
 
536
- df = self.get_samples_metadata(project_id=project_id, df=True)
578
+ df = self.get_samples(project_id=project_id, as_df=True)
537
579
 
538
580
  if filter == "control":
539
581
  df = df[~df["control"].isna()]
@@ -546,7 +588,7 @@ class SeerSDK:
546
588
 
547
589
  return valid_samples
548
590
 
549
- def get_sample_custom_fields(self):
591
+ def _get_sample_custom_fields(self):
550
592
  """
551
593
  Fetches a list of custom fields defined for the authenticated user.
552
594
  """
@@ -566,7 +608,7 @@ class SeerSDK:
566
608
  del entry["tenant_id"]
567
609
  return res
568
610
 
569
- def get_msdata(self, sample_ids: list, df: bool = False):
611
+ def get_msruns(self, sample_ids: list, as_df: bool = False):
570
612
  """
571
613
  Fetches MS data files for passed in `sample_ids` (provided they are valid and contain relevant files) for an authenticated user.
572
614
 
@@ -576,12 +618,12 @@ class SeerSDK:
576
618
  ----------
577
619
  sample_ids : list
578
620
  List of unique sample IDs.
579
- df: bool
580
- Boolean denoting whether the user wants the response back in JSON or a DataFrame object.
621
+ as_df: bool
622
+ whether the result should be converted to a DataFrame, defaulted to False.
581
623
 
582
624
  Returns
583
625
  -------
584
- res: list or DataFrame
626
+ res: list[dict] or DataFrame
585
627
  List/DataFrame of plate objects for the authenticated user.
586
628
 
587
629
  Examples
@@ -590,13 +632,13 @@ class SeerSDK:
590
632
  >>> seer_sdk = SeerSDK()
591
633
  >>> sample_ids = ["812139c0-15e0-11ee-bdf1-bbaa73585acf", "803e05b0-15e0-11ee-bdf1-bbaa73585acf"]
592
634
 
593
- >>> seer_sdk.get_msdata(sample_ids)
635
+ >>> seer_sdk.get_msruns(sample_ids)
594
636
  >>> [
595
637
  {"id": "SAMPLE_ID_1_HERE" ... },
596
638
  {"id": "SAMPLE_ID_2_HERE" ... }
597
639
  ]
598
640
 
599
- >>> seer_sdk.get_msdata(sample_ids, df=True)
641
+ >>> seer_sdk.get_msruns(sample_ids, as_df=True)
600
642
  >>> id ... gradient
601
643
  0 81c6a180-15e0-11ee-bdf1-bbaa73585acf ... None
602
644
  1 816a9ed0-15e0-11ee-bdf1-bbaa73585acf ... None
@@ -631,209 +673,7 @@ class SeerSDK:
631
673
  entry["raw_file_path"] = entry["raw_file_path"][
632
674
  location(entry["raw_file_path"]) :
633
675
  ]
634
- return res if not df else dict_to_df(res)
635
-
636
- def get_plate(self, plate_id: str, df: bool = False):
637
- """
638
- Fetches MS data files for a `plate_id` (provided that the `plate_id` is valid and has samples associated with it) for an authenticated user.
639
-
640
- 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.
641
-
642
- Parameters
643
- ----------
644
- plate_id : str, optional
645
- ID of the plate for which samples are to be fetched, defaulted to None.
646
- df: bool
647
- Boolean denoting whether the user wants the response back in JSON or a DataFrame object
648
-
649
- Returns
650
- -------
651
- res: list or DataFrame
652
- List/DataFrame of MS data file objects for the authenticated user.
653
-
654
- Examples
655
- -------
656
- >>> from seer_pas_sdk import SeerSDK
657
- >>> seer_sdk = SeerSDK()
658
- >>> plate_id = "7ec8cad0-15e0-11ee-bdf1-bbaa73585acf"
659
-
660
- >>> seer_sdk.get_plate(plate_id)
661
- >>> [
662
- {"id": "PLATE_ID_1_HERE" ... },
663
- {"id": "PLATE_ID_2_HERE" ... }
664
- ]
665
-
666
- >>> seer_sdk.get_plate(plate_id, df=True)
667
- >>> id ... volume
668
- 0 PLATE_ID_1_HERE ... None
669
- 1 PLATE_ID_2_HERE ... None
670
-
671
- [2 rows x 26 columns]
672
- """
673
- plate_samples = self.get_samples_metadata(plate_id=plate_id)
674
- sample_ids = [sample["id"] for sample in plate_samples]
675
- return self.get_msdata(sample_ids, df)
676
-
677
- def get_project(
678
- self,
679
- project_id: str,
680
- msdata: bool = False,
681
- df: bool = False,
682
- flat: bool = False,
683
- ):
684
- """
685
- Fetches samples (and MS data files) for a `project_id` (provided that the `project_id` is valid and has samples associated with it) for an authenticated user.
686
-
687
- The function returns a DataFrame object if the `df` flag is passed in as True, otherwise a nested dict object is returned instead. If the both the `df` and `msdata` flags are passed in as True, then a nested DataFrame object is returned instead.
688
-
689
- If the `flat` flag is passed in as True, then the nested dict object is returned as an array of dict objects and the nested df object is returned as a single df object.
690
-
691
- Parameters
692
- ----------
693
- project_id : str
694
- ID of the project for which samples are to be fetched.
695
- msdata: bool, optional
696
- Boolean flag denoting whether the user wants relevant MS data files associated with the samples.
697
- df: bool, optional
698
- Boolean denoting whether the user wants the response back in JSON or a DataFrame object.
699
-
700
- Returns
701
- -------
702
- res: list or DataFrame
703
- List/DataFrame of plate objects for the authenticated user.
704
-
705
- Examples
706
- -------
707
- >>> from seer_pas_sdk import SeerSDK
708
- >>> seer_sdk = SeerSDK()
709
- >>> project_id = "7e48e150-8a47-11ed-b382-bf440acece26"
710
-
711
- >>> seer_sdk.get_project(project_id=project_id, msdata=False, df=False)
712
- >>> {
713
- "project_samples": [
714
- {
715
- "id": "SAMPLE_ID_1_HERE",
716
- "sample_type": "Plasma",
717
- ...
718
- ...
719
- },
720
- {
721
- "id": "SAMPLE_ID_2_HERE",
722
- "sample_type": "Plasma",
723
- ...
724
- ...
725
- }
726
- ]
727
- }
728
-
729
- >>> seer_sdk.get_project(project_id=project_id, msdata=True, df=False)
730
- >>> [
731
- {
732
- "id": "SAMPLE_ID_1_HERE",
733
- "sample_type": "Plasma",
734
- ...
735
- ...
736
- "ms_data_files": [
737
- {
738
- "id": MS_DATA_FILE_ID_1_HERE,
739
- "tenant_id": "TENANT_ID_HERE",
740
- ...
741
- ...
742
- },
743
- {
744
- "id": MS_DATA_FILE_ID_1_HERE,
745
- "tenant_id": "TENANT_ID_HERE",
746
- ...
747
- ...
748
- }
749
- ]
750
- },
751
- {
752
- "id": "SAMPLE_ID_2_HERE",
753
- "sample_type": "Plasma",
754
- ...
755
- ...
756
- "ms_data_files": [
757
- {
758
- "id": MS_DATA_FILE_ID_2_HERE,
759
- "tenant_id": "TENANT_ID_HERE",
760
- ...
761
- ...
762
- },
763
- {
764
- "id": MS_DATA_FILE_ID_2_HERE,
765
- "tenant_id": "TENANT_ID_HERE",
766
- ...
767
- ...
768
- }
769
- ]
770
- }
771
- ]
772
-
773
- >>> seer_sdk.get_project(project_id=project_id, msdata=True, df=True)
774
- >>> id ... ms_data_files
775
- 0 829509f0-8a47-11ed-b382-bf440acece26 ... id ... g...
776
- 1 828d41c0-8a47-11ed-b382-bf440acece26 ... id ... g...
777
- 2 8294e2e0-8a47-11ed-b382-bf440acece26 ... id ... g...
778
- 3 8285eec0-8a47-11ed-b382-bf440acece26 ... id ... g...
779
-
780
- [4 rows x 60 columns]
781
- """
782
- if not project_id:
783
- return ValueError("No project ID specified.")
784
-
785
- sample_ids = []
786
- project_samples = self.get_samples_metadata(
787
- project_id=project_id, df=False
788
- )
789
- flat_result = []
790
-
791
- if msdata:
792
-
793
- # construct map for quick index reference of sample in project_samples
794
- sample_ids = {
795
- sample["id"]: i for i, sample in enumerate(project_samples)
796
- } # will always contain unique values
797
- ms_data_files = self.get_msdata(
798
- sample_ids=list(sample_ids.keys()), df=False
799
- )
800
-
801
- for ms_data_file in ms_data_files:
802
- index = sample_ids.get(ms_data_file["sample_id"], None)
803
- if not index:
804
- continue
805
-
806
- if not flat:
807
- if "ms_data_file" not in project_samples[index]:
808
- project_samples[index]["ms_data_files"] = [
809
- ms_data_file
810
- ]
811
- else:
812
- project_samples[index]["ms_data_files"].append(
813
- ms_data_file
814
- )
815
- else:
816
- flat_result.append(project_samples[index] | ms_data_file)
817
-
818
- # return flat result if results were added to the flat object
819
- if flat and flat_result:
820
- project_samples = flat_result
821
-
822
- if df:
823
- if flat:
824
- return pd.DataFrame(project_samples)
825
- else:
826
- for sample_index in range(len(project_samples)):
827
- if "ms_data_files" in project_samples[sample_index]:
828
- project_samples[sample_index]["ms_data_files"] = (
829
- dict_to_df(
830
- project_samples[sample_index]["ms_data_files"]
831
- )
832
- )
833
-
834
- project_samples = dict_to_df(project_samples)
835
-
836
- return project_samples
676
+ return res if not as_df else dict_to_df(res)
837
677
 
838
678
  def get_analysis_protocols(
839
679
  self,
@@ -853,7 +693,7 @@ class SeerSDK:
853
693
 
854
694
  Returns
855
695
  -------
856
- protocols: list
696
+ protocols: list[dict]
857
697
  List of analysis protocol objects for the authenticated user.
858
698
 
859
699
  Examples
@@ -920,7 +760,7 @@ class SeerSDK:
920
760
 
921
761
  return res
922
762
 
923
- def get_analysis(
763
+ def get_analyses(
924
764
  self,
925
765
  analysis_id: str = None,
926
766
  folder_id: str = None,
@@ -962,30 +802,30 @@ class SeerSDK:
962
802
 
963
803
  Returns
964
804
  -------
965
- analyses: dict
805
+ analyses: list[dict]
966
806
  Contains a list of analyses objects for the authenticated user.
967
807
 
968
808
  Examples
969
809
  -------
970
810
  >>> from seer_pas_sdk import SeerSDK
971
811
  >>> seer_sdk = SeerSDK()
972
- >>> seer_sdk.get_analysis()
812
+ >>> seer_sdk.get_analyses()
973
813
  >>> [
974
814
  {id: "YOUR_ANALYSIS_ID_HERE", ...},
975
815
  {id: "YOUR_ANALYSIS_ID_HERE", ...},
976
816
  {id: "YOUR_ANALYSIS_ID_HERE", ...}
977
817
  ]
978
818
 
979
- >>> seer_sdk.get_analysis("YOUR_ANALYSIS_ID_HERE")
819
+ >>> seer_sdk.get_analyses("YOUR_ANALYSIS_ID_HERE")
980
820
  >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
981
821
 
982
- >>> seer_sdk.get_analysis(folder_name="YOUR_FOLDER_NAME_HERE")
822
+ >>> seer_sdk.get_analyses(folder_name="YOUR_FOLDER_NAME_HERE")
983
823
  >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
984
824
 
985
- >>> seer_sdk.get_analysis(analysis_name="YOUR_ANALYSIS")
825
+ >>> seer_sdk.get_analyses(analysis_name="YOUR_ANALYSIS")
986
826
  >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
987
827
 
988
- >>> seer_sdk.get_analysis(description="YOUR_DESCRIPTION")
828
+ >>> seer_sdk.get_analyses(description="YOUR_DESCRIPTION")
989
829
  >>> [{ id: "YOUR_ANALYSIS_ID_HERE", ...}]
990
830
  """
991
831
 
@@ -1074,7 +914,7 @@ class SeerSDK:
1074
914
 
1075
915
  # recursive solution to get analyses in folders
1076
916
  for folder in folders:
1077
- res += self.get_analysis(folder_id=folder)
917
+ res += self.get_analyses(folder_id=folder)
1078
918
 
1079
919
  if analysis_only:
1080
920
  res = [
@@ -1082,6 +922,7 @@ class SeerSDK:
1082
922
  ]
1083
923
  return res
1084
924
 
925
+ @deprecation.deprecated(deprecated_in="0.3.0", removed_in="1.0.0")
1085
926
  def get_analysis_result_protein_data(
1086
927
  self, analysis_id: str, link: bool = False, pg: str = None
1087
928
  ):
@@ -1154,6 +995,7 @@ class SeerSDK:
1154
995
  "protein_panel": protein_panel,
1155
996
  }
1156
997
 
998
+ @deprecation.deprecated(deprecated_in="0.3.0", removed_in="1.0.0")
1157
999
  def get_analysis_result_peptide_data(
1158
1000
  self, analysis_id: str, link: bool = False, peptide: str = None
1159
1001
  ):
@@ -1229,7 +1071,92 @@ class SeerSDK:
1229
1071
  "peptide_panel": peptide_panel,
1230
1072
  }
1231
1073
 
1232
- def list_analysis_result_files(self, analysis_id: str):
1074
+ def _get_search_result_protein_data(self, analysis_id: str):
1075
+ """
1076
+ Given an analysis id, this function returns the protein data for the analysis.
1077
+
1078
+ Parameters
1079
+ ----------
1080
+ analysis_id : str
1081
+ ID of the analysis for which the data is to be fetched.
1082
+ """
1083
+ with self._get_auth_session() as s:
1084
+ URL = f"{self._auth.url}api/v1/data"
1085
+ response = s.get(
1086
+ f"{URL}/protein?analysisId={analysis_id}&retry=false"
1087
+ )
1088
+
1089
+ if response.status_code != 200:
1090
+ raise ValueError(
1091
+ "Could not fetch protein data. Please verify that your analysis completed."
1092
+ )
1093
+ response = response.json()
1094
+
1095
+ protein_data = {}
1096
+ for row in response:
1097
+ if row.get("name") == "npLink":
1098
+ protein_data["npLink"] = {
1099
+ "url": row.get("link", {}).get("url", "")
1100
+ }
1101
+ if row.get("name") == "panelLink":
1102
+ protein_data["panelLink"] = {
1103
+ "url": row.get("link", {}).get("url", "")
1104
+ }
1105
+ if not protein_data:
1106
+ raise ValueError("No protein result files found.")
1107
+ if not "panelLink" in protein_data.keys():
1108
+ protein_data["panelLink"] = {"url": ""}
1109
+
1110
+ return protein_data
1111
+
1112
+ def _get_search_result_peptide_data(self, analysis_id: str):
1113
+ """
1114
+ Given an analysis id, this function returns the peptide data for the analysis.
1115
+
1116
+ Parameters
1117
+ ----------
1118
+
1119
+ analysis_id : str
1120
+ ID of the analysis for which the data is to be fetched.
1121
+
1122
+ Returns
1123
+ -------
1124
+ peptide_data : dict[str, str]
1125
+ Dictionary containing URLs for npLink and panelLink peptide data.
1126
+
1127
+ """
1128
+
1129
+ with self._get_auth_session() as s:
1130
+ URL = f"{self._auth.url}api/v1/data"
1131
+ response = s.get(
1132
+ f"{URL}/peptide?analysisId={analysis_id}&retry=false"
1133
+ )
1134
+
1135
+ if response.status_code != 200:
1136
+ raise ValueError(
1137
+ "Could not fetch peptide data. Please verify that your analysis completed."
1138
+ )
1139
+
1140
+ response = response.json()
1141
+
1142
+ peptide_data = {}
1143
+ for row in response:
1144
+ if row.get("name") == "npLink":
1145
+ peptide_data["npLink"] = {
1146
+ "url": row.get("link", {}).get("url", "")
1147
+ }
1148
+ if row.get("name") == "panelLink":
1149
+ peptide_data["panelLink"] = {
1150
+ "url": row.get("link", {}).get("url", "")
1151
+ }
1152
+ if not peptide_data:
1153
+ raise ValueError("No peptide result files found.")
1154
+ if not "panelLink" in peptide_data.keys():
1155
+ peptide_data["panelLink"] = {"url": ""}
1156
+
1157
+ return peptide_data
1158
+
1159
+ def list_search_result_files(self, analysis_id: str):
1233
1160
  """
1234
1161
  Given an analysis id, this function returns a list of files associated with the analysis.
1235
1162
 
@@ -1240,11 +1167,11 @@ class SeerSDK:
1240
1167
 
1241
1168
  Returns
1242
1169
  -------
1243
- files: list
1170
+ files: list[str]
1244
1171
  List of files associated with the analysis.
1245
1172
  """
1246
1173
  try:
1247
- analysis_metadata = self.get_analysis(analysis_id)[0]
1174
+ analysis_metadata = self.get_analyses(analysis_id)[0]
1248
1175
  except (IndexError, ServerError):
1249
1176
  raise ValueError("Invalid analysis ID.")
1250
1177
  except:
@@ -1266,7 +1193,141 @@ class SeerSDK:
1266
1193
  files.append(row["filename"])
1267
1194
  return files
1268
1195
 
1269
- def get_analysis_result_file_url(self, analysis_id: str, filename: str):
1196
+ def get_search_result(
1197
+ self, analysis_id: str, analyte_type: str, rollup: str
1198
+ ):
1199
+ """
1200
+ Load one of the files available via the "Download result files" button on the PAS UI.
1201
+
1202
+ Args:
1203
+ analysis_id (str): id of the analysis
1204
+ analyte_type (str): type of the data. Acceptable options are one of ['protein', 'peptide', 'precursor'].
1205
+ rollup (str): the desired file. Acceptable options are one of ['np', 'panel'].
1206
+ Returns:
1207
+ pd.DataFrame: the requested file as a pandas DataFrame
1208
+
1209
+ """
1210
+ if not analysis_id:
1211
+ raise ValueError("Analysis ID cannot be empty.")
1212
+
1213
+ if analyte_type not in ["protein", "peptide", "precursor"]:
1214
+ raise ValueError(
1215
+ "Invalid data type. Please choose between 'protein', 'peptide', or 'precursor'."
1216
+ )
1217
+
1218
+ if rollup not in ["np", "panel"]:
1219
+ raise ValueError(
1220
+ "Invalid file. Please choose between 'np', 'panel'."
1221
+ )
1222
+
1223
+ if analyte_type == "precursor" and rollup == "panel":
1224
+ raise ValueError(
1225
+ "Precursor data is not available for panel rollup, please select np rollup."
1226
+ )
1227
+
1228
+ if analyte_type == "protein":
1229
+ if rollup == "np":
1230
+ return url_to_df(
1231
+ self._get_search_result_protein_data(analysis_id)[
1232
+ "npLink"
1233
+ ]["url"]
1234
+ )
1235
+ elif rollup == "panel":
1236
+ return url_to_df(
1237
+ self._get_search_result_protein_data(analysis_id)[
1238
+ "panelLink"
1239
+ ]["url"]
1240
+ )
1241
+ elif analyte_type == "peptide":
1242
+ if rollup == "np":
1243
+ return url_to_df(
1244
+ self._get_search_result_peptide_data(analysis_id)[
1245
+ "npLink"
1246
+ ]["url"]
1247
+ )
1248
+ elif rollup == "panel":
1249
+ return url_to_df(
1250
+ self._get_search_result_peptide_data(analysis_id)[
1251
+ "panelLink"
1252
+ ]["url"]
1253
+ )
1254
+ else:
1255
+ return url_to_df(
1256
+ self.get_search_result_file_url(
1257
+ analysis_id, filename="report.tsv"
1258
+ )["url"]
1259
+ )
1260
+
1261
+ def download_search_output_file(
1262
+ self, analysis_id: str, filename: str, download_path: str = ""
1263
+ ):
1264
+ """
1265
+ Given an analysis id and a analysis result filename, this function downloads the file to the specified path.
1266
+
1267
+ Parameters
1268
+ ----------
1269
+ analysis_id : str
1270
+ ID of the analysis for which the data is to be fetched.
1271
+
1272
+ filename : str
1273
+ Name of the file to be fetched. Files can be case insensitive and without file extensions.
1274
+
1275
+ download_path : str
1276
+ String flag denoting where the user wants the files downloaded. Can be local or absolute as long as the path is valid.
1277
+
1278
+ Returns
1279
+ -------
1280
+ None
1281
+ Downloads the file to the specified path.
1282
+ """
1283
+
1284
+ if not download_path:
1285
+ download_path = os.getcwd()
1286
+
1287
+ if not analysis_id:
1288
+ raise ValueError("Analysis ID cannot be empty.")
1289
+
1290
+ if not os.path.exists(download_path):
1291
+ raise ValueError(
1292
+ "Please specify a valid folder path as download path."
1293
+ )
1294
+
1295
+ file = self.get_search_result_file_url(analysis_id, filename)
1296
+ file_url = file["url"]
1297
+ filename = file["filename"]
1298
+
1299
+ print("Downloading file:", filename)
1300
+ for _ in range(2):
1301
+ try:
1302
+ with tqdm(
1303
+ unit="B",
1304
+ unit_scale=True,
1305
+ unit_divisor=1024,
1306
+ miniters=1,
1307
+ desc=f"Progress",
1308
+ ) as t:
1309
+ ssl._create_default_https_context = (
1310
+ ssl._create_unverified_context
1311
+ )
1312
+ urllib.request.urlretrieve(
1313
+ file_url,
1314
+ f"{download_path}/{filename}",
1315
+ reporthook=download_hook(t),
1316
+ data=None,
1317
+ )
1318
+ break
1319
+ except:
1320
+ filename = filename.split("/")
1321
+ name += "/" + "/".join(
1322
+ [filename[i] for i in range(len(filename) - 1)]
1323
+ )
1324
+ filename = filename[-1]
1325
+ if not os.path.isdir(f"{name}/{filename}"):
1326
+ os.makedirs(f"{name}/")
1327
+ print(f"File {filename} downloaded successfully to {download_path}.")
1328
+ return
1329
+
1330
+ def get_search_result_file_url(self, analysis_id: str, filename: str):
1270
1331
  """
1271
1332
  Given an analysis id and a analysis result filename, this function returns the signed URL for the file.
1272
1333
 
@@ -1280,21 +1341,29 @@ class SeerSDK:
1280
1341
 
1281
1342
  Returns
1282
1343
  -------
1283
- file_url: dict
1284
- Response object containing the url for the file.
1344
+ file_url: dict[str, str]
1345
+ Dictionary containing the 'url' and 'filename' of the file.
1285
1346
  """
1347
+ if "." in filename:
1348
+ filename = ".".join(filename.split(".")[:-1])
1349
+ filename = filename.casefold()
1286
1350
 
1287
1351
  # Allow user to pass in filenames without an extension.
1288
- analysis_result_files = self.list_analysis_result_files(analysis_id)
1352
+ analysis_result_files = self.list_search_result_files(analysis_id)
1289
1353
  analysis_result_files_prefix_mapper = {
1290
- ".".join(x.split(".")[:-1]): x for x in analysis_result_files
1354
+ (".".join(x.split(".")[:-1])).casefold(): x
1355
+ for x in analysis_result_files
1291
1356
  }
1292
1357
  if filename in analysis_result_files_prefix_mapper:
1293
1358
  filename = analysis_result_files_prefix_mapper[filename]
1359
+ else:
1360
+ raise ValueError(
1361
+ 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."
1362
+ )
1294
1363
 
1295
- analysis_metadata = self.get_analysis(analysis_id)[0]
1364
+ analysis_metadata = self.get_analyses(analysis_id)[0]
1296
1365
  if analysis_metadata.get("status") in ["Failed", None]:
1297
- raise ValueError("Cannot generate links for failed analyses.")
1366
+ raise ValueError("Cannot generate links for failed searches.")
1298
1367
  with self._get_auth_session() as s:
1299
1368
  file_url = s.post(
1300
1369
  f"{self._auth.url}api/v1/analysisResultFiles/getUrl",
@@ -1307,8 +1376,11 @@ class SeerSDK:
1307
1376
  response = file_url.json()
1308
1377
  if not response.get("url"):
1309
1378
  raise ValueError(f"File {filename} not found.")
1379
+
1380
+ response["filename"] = filename
1310
1381
  return response
1311
1382
 
1383
+ @deprecation.deprecated(deprecated_in="0.3.0", removed_in="1.0.0")
1312
1384
  def get_analysis_result_files(
1313
1385
  self,
1314
1386
  analysis_id: str,
@@ -1339,7 +1411,7 @@ class SeerSDK:
1339
1411
 
1340
1412
  Returns
1341
1413
  -------
1342
- links: dict
1414
+ links: dict[str, pd.DataFrame]
1343
1415
  Contains dataframe objects for the requested files. If a filename is not found, it is skipped.
1344
1416
 
1345
1417
 
@@ -1389,7 +1461,7 @@ class SeerSDK:
1389
1461
 
1390
1462
  filenames = set(filenames)
1391
1463
  # Allow user to pass in filenames without an extension.
1392
- analysis_result_files = self.list_analysis_result_files(analysis_id)
1464
+ analysis_result_files = self.list_search_result_files(analysis_id)
1393
1465
  analysis_result_files_prefix_mapper = {
1394
1466
  ".".join(x.split(".")[:-1]): x for x in analysis_result_files
1395
1467
  }
@@ -1426,7 +1498,7 @@ class SeerSDK:
1426
1498
  links["peptide_panel.tsv"] = peptide_data["panelLink"]["url"]
1427
1499
  else:
1428
1500
  try:
1429
- links[filename] = self.get_analysis_result_file_url(
1501
+ links[filename] = self._get_search_result_file_url(
1430
1502
  analysis_id, filename
1431
1503
  )["url"]
1432
1504
  except Exception as e:
@@ -1451,6 +1523,7 @@ class SeerSDK:
1451
1523
 
1452
1524
  return links
1453
1525
 
1526
+ @deprecation.deprecated(deprecated_in="0.3.0", removed_in="1.0.0")
1454
1527
  def get_analysis_result(
1455
1528
  self,
1456
1529
  analysis_id: str,
@@ -1522,7 +1595,7 @@ class SeerSDK:
1522
1595
  }
1523
1596
 
1524
1597
  if diann_report:
1525
- diann_report_url = self.get_analysis_result_file_url(
1598
+ diann_report_url = self._get_search_result_file_url(
1526
1599
  analysis_id, "report.tsv"
1527
1600
  )
1528
1601
  links["diann_report"] = url_to_df(diann_report_url["url"])
@@ -1578,7 +1651,7 @@ class SeerSDK:
1578
1651
  raise ValueError("Analysis id cannot be empty.")
1579
1652
 
1580
1653
  try:
1581
- res = self.get_analysis(analysis_id)
1654
+ res = self.get_analyses(analysis_id)
1582
1655
  except ValueError:
1583
1656
  return ValueError("Analysis not found. Your ID could be incorrect")
1584
1657
 
@@ -1597,7 +1670,7 @@ class SeerSDK:
1597
1670
 
1598
1671
  Returns
1599
1672
  -------
1600
- list
1673
+ list[str]
1601
1674
  Contains the list of files in the folder.
1602
1675
 
1603
1676
  Examples
@@ -1652,8 +1725,8 @@ class SeerSDK:
1652
1725
 
1653
1726
  Returns
1654
1727
  -------
1655
- message: dict
1656
- Contains the message whether the files were downloaded or not.
1728
+ message: dict[str, str]
1729
+ Contains the 'message' whether the files were downloaded or not.
1657
1730
  """
1658
1731
 
1659
1732
  urls = []
@@ -1756,6 +1829,11 @@ class SeerSDK:
1756
1829
  **kwargs : dict, optional
1757
1830
  Search keyword parameters to be passed in. Acceptable values are 'name' or 'description'.
1758
1831
 
1832
+ Returns
1833
+ -------
1834
+ res : list[dict]
1835
+ A list of dictionaries containing the group analysis objects.
1836
+
1759
1837
  """
1760
1838
  params = {"analysisid": analysis_id}
1761
1839
  if kwargs and not group_analysis_id:
@@ -1807,7 +1885,7 @@ class SeerSDK:
1807
1885
  Returns
1808
1886
  -------
1809
1887
  res : dict
1810
- A dictionary containing the group analysis data.
1888
+ A dictionary containing the group analysis object.
1811
1889
 
1812
1890
  Examples
1813
1891
  -------
@@ -1961,7 +2039,7 @@ class SeerSDK:
1961
2039
  analysis_id (str): ID of the analysis.
1962
2040
  feature_ids (list[str], optional): Filter result object to a set of ids. Defaults to [].
1963
2041
  show_significant_only (bool, optional): Mark true if only significant results are to be returned. Defaults to False.
1964
- as_df (bool, optional): Mark true if return object should be a pandas DataFrame. Defaults to False.
2042
+ as_df (bool, optional): whether the result should be converted to a DataFrame. Defaults to False.
1965
2043
  volcano_plot (bool, optional): Mark true to include the volcano plot data in the return object. Defaults to False.
1966
2044
  cached (bool, optional): Mark true to return volcano plot data as a VolcanoPlotBuilder object. No effect if volcano_plot flag is marked false. Defaults to False.
1967
2045
 
@@ -1983,8 +2061,10 @@ class SeerSDK:
1983
2061
 
1984
2062
  protein_peptide_gene_map = builder.protein_gene_map
1985
2063
 
1986
- # API call 2 - get analysis samples metadata to get condition
1987
- samples_metadata = self.get_analysis_samples(analysis_id)
2064
+ # API call 2 - get analysis samples to get condition
2065
+ samples_metadata = self._get_analysis_samples(
2066
+ analysis_id=analysis_id
2067
+ )
1988
2068
 
1989
2069
  json = {"analysisId": analysis_id}
1990
2070
  if feature_ids:
@@ -2021,7 +2101,7 @@ class SeerSDK:
2021
2101
  if x[feature_type_index] in protein_peptide_gene_map
2022
2102
  ]
2023
2103
  sample_id_condition = {
2024
- x["id"]: x["condition"] for x in samples_metadata[0]["samples"]
2104
+ x["id"]: x["condition"] for x in samples_metadata
2025
2105
  }
2026
2106
 
2027
2107
  if show_significant_only:
@@ -2062,7 +2142,7 @@ class SeerSDK:
2062
2142
  box_plot (bool, optional): Mark true to include box plot data in the return object. Defaults to False.
2063
2143
 
2064
2144
  Returns:
2065
- dict: A dictionary containing the volcano plot and optionally box plot data for each group analysis.
2145
+ dict[str, pd.DataFrame]: A dictionary containing the volcano plot and optionally box plot data for each group analysis.
2066
2146
  """
2067
2147
  group_analysis_ids = [
2068
2148
  x["id"]
@@ -2118,8 +2198,8 @@ class SeerSDK:
2118
2198
  ValueError: Invalid type provided.
2119
2199
  ServerError: Could not fetch PCA data.
2120
2200
  Returns:
2121
- dict
2122
- Pure response from the API.
2201
+ dict[str, list|float]
2202
+ Returns response object containing 'xContributionRatio' (float), 'yContributionRatio' (float), 'samples' (list[dict]), and 'points' (list[float]).
2123
2203
  """
2124
2204
  if not analysis_ids:
2125
2205
  raise ValueError("Analysis IDs cannot be empty.")
@@ -2164,7 +2244,7 @@ class SeerSDK:
2164
2244
  type (str): Type of data to be fetched. Must be either 'protein' or 'peptide'.
2165
2245
  sample_ids (list[str], optional): IDs of the samples of interest.
2166
2246
  hide_control (bool, optional): Mark true if controls are to be excluded. Defaults to False.
2167
- as_df (bool, optional): Mark true if the data should be returned as a pandas DataFrame. Defaults to False.
2247
+ as_df (bool, optional): whether the result should be converted to a DataFrame. Defaults to False.
2168
2248
  Raises:
2169
2249
  ValueError: No analysis IDs provided.
2170
2250
  ValueError: No sample IDs provided.
@@ -2491,7 +2571,7 @@ class SeerSDK:
2491
2571
  fold_change_threshold (float, optional): Cutoff value for the fold change to determine significance. Defaults to 1.
2492
2572
  label_by (str, optional): Metric to sort result data. Defaults to "fold_change".
2493
2573
  cached (bool, optional): Return a VolcanoPlotBuilder object for calculation reuse. Defaults to False.
2494
- as_df (bool, optional): Return data as a pandas DataFrame. Defaults to False.
2574
+ as_df (bool, optional): whether the result should be converted to a DataFrame. Defaults to False.
2495
2575
 
2496
2576
  Raises:
2497
2577
  ServerError - could not fetch group analysis results.
@@ -2521,29 +2601,51 @@ class SeerSDK:
2521
2601
  else:
2522
2602
  return obj.volcano_plot
2523
2603
 
2524
- def get_analysis_samples(self, analysis_id: str):
2604
+ def _get_analysis_samples(
2605
+ self, analysis_id: str = None, analysis_name: str = None, as_df=False
2606
+ ):
2525
2607
  """
2526
- Get the samples associated with a given analysis ID.
2608
+ Get the samples associated with a given analysis.
2527
2609
 
2528
2610
  Args:
2529
- analysis_id (str): The analysis ID.
2611
+ analysis_id (str): UUID identifier of the analysis. Defaults to None.
2612
+ analysis_name (str): Name of the analysis. Defaults to None.
2613
+ as_df (bool) : whether the result should be converted to a DataFrame. Defaults to False.
2530
2614
 
2531
2615
  Raises:
2532
2616
  ServerError - could not retrieve samples for analysis.
2533
2617
  Returns:
2534
- dict: A dictionary containing the samples associated with the analysis.
2618
+ list[dict] : a list of samples associated with the analysis.
2535
2619
  """
2536
- if not analysis_id:
2537
- raise ValueError("Analysis ID cannot be empty.")
2538
2620
 
2539
- URL = f"{self._auth.url}api/v1/analyses/samples/{analysis_id}"
2540
- with self._get_auth_session() as s:
2541
- samples = s.get(URL)
2621
+ if not analysis_id and not analysis_name:
2622
+ raise ValueError("Analysis cannot be empty.")
2542
2623
 
2543
- if samples.status_code != 200:
2544
- raise ServerError("Could not retrieve samples for analysis.")
2624
+ if analysis_id:
2625
+ rows = [{"id": analysis_id}]
2626
+ else:
2627
+ rows = self.get_analyses(analysis_name=analysis_name)
2628
+
2629
+ resp = []
2630
+ for row in rows:
2631
+ URL = f"{self._auth.url}api/v1/analyses/samples/{row['id']}"
2632
+ with self._get_auth_session() as s:
2633
+ samples = s.get(URL)
2634
+ try:
2635
+ samples.raise_for_status()
2636
+ obj = samples.json()[0]
2637
+ resp += obj["samples"]
2638
+ except:
2639
+ continue
2640
+
2641
+ if not resp:
2642
+ raise ServerError(
2643
+ f"Could not retrieve samples for analysis {analysis_id or analysis_name}."
2644
+ )
2545
2645
 
2546
- return samples.json()
2646
+ resp = pd.DataFrame(resp)
2647
+ resp.drop_duplicates(subset=["id"], inplace=True)
2648
+ return resp if as_df else resp.to_dict(orient="records")
2547
2649
 
2548
2650
  def get_analysis_protocol_fasta(self, analysis_id, download_path=None):
2549
2651
  if not analysis_id:
@@ -2553,7 +2655,7 @@ class SeerSDK:
2553
2655
  download_path = os.getcwd()
2554
2656
 
2555
2657
  try:
2556
- analysis_protocol_id = self.get_analysis(analysis_id)[0][
2658
+ analysis_protocol_id = self.get_analyses(analysis_id)[0][
2557
2659
  "analysis_protocol_id"
2558
2660
  ]
2559
2661
  except (IndexError, KeyError):
@@ -277,7 +277,7 @@ class _UnsupportedSDK(_SeerSDK):
277
277
  samples = (
278
278
  x["id"]
279
279
  for plate_id in plates
280
- for x in self.get_samples_metadata(plate_id=plate_id)
280
+ for x in self.get_samples(plate_id=plate_id)
281
281
  )
282
282
 
283
283
  return self.add_samples_to_project(
@@ -743,7 +743,7 @@ class _UnsupportedSDK(_SeerSDK):
743
743
  if sample_ids:
744
744
  valid_ids = [
745
745
  entry["id"]
746
- for entry in self.get_samples_metadata(project_id=project_id)
746
+ for entry in self.get_samples(project_id=project_id)
747
747
  ]
748
748
 
749
749
  for sample_id in sample_ids:
@@ -1166,7 +1166,7 @@ class _UnsupportedSDK(_SeerSDK):
1166
1166
  raise ValueError("Analysis ID cannot be empty.")
1167
1167
 
1168
1168
  try:
1169
- valid_analysis = self.get_analysis(analysis_id)[0]
1169
+ valid_analysis = self.get_analyses(analysis_id)[0]
1170
1170
  except:
1171
1171
  raise ValueError(
1172
1172
  "Invalid analysis ID. Please check if the analysis ID is valid or the backend is running."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seer-pas-sdk
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: SDK for Seer Proteograph Analysis Suite (PAS)
5
5
  Author-email: Ryan Sun <rsun@seer.bio>
6
6
  License:
@@ -199,6 +199,7 @@ Requires-Dist: PyJWT>=2.8.0
199
199
  Requires-Dist: python-dotenv>=1.0.0
200
200
  Requires-Dist: Requests>=2.31.0
201
201
  Requires-Dist: tqdm>=4.65.0
202
+ Requires-Dist: deprecation
202
203
  Dynamic: license-file
203
204
 
204
205
  # Seer PAS Python SDK
@@ -5,14 +5,14 @@ seer_pas_sdk/common/__init__.py,sha256=jA5qm-t9x3qXMp5Q6v7vV2kZdLw32-lnbtf0fPY4l
5
5
  seer_pas_sdk/common/errors.py,sha256=4HFORWnaQQCMXRE8kwdsJWvQRB_3KFEZ7yMb391e4gA,142
6
6
  seer_pas_sdk/common/groupanalysis.py,sha256=DxB-gbQfYzl7p9MTYWDIqghcH-IeakzdYdrRZrlIHek,1730
7
7
  seer_pas_sdk/core/__init__.py,sha256=rxbKgg-Qe24OaxX2zyHHYPYgDCTEKE_-41bB2wvpvL4,25
8
- seer_pas_sdk/core/sdk.py,sha256=fUbskM4zxhVkK8i9lt6dJJi32ss4KVO3ZNOF3OB1S7c,97689
9
- seer_pas_sdk/core/unsupported.py,sha256=FqaJrkctNIStr1YTyV2nZc4qmgje3yza221z4Y28zpM,59908
8
+ seer_pas_sdk/core/sdk.py,sha256=Wt-cB1hB0dMZLDg0oZvO-NPGzKR70dCAdrZ4xKnC2zo,101286
9
+ seer_pas_sdk/core/unsupported.py,sha256=XAPZ3tidqjnsgftf3NUdTGIzvnsjHy0e_eGRCAo6GPo,59890
10
10
  seer_pas_sdk/objects/__init__.py,sha256=HJLS6sOr7DfzdI14fv5dWcITEj5QQsKcdfED3YNvUrY,107
11
11
  seer_pas_sdk/objects/groupanalysis.py,sha256=x3D_5NmYBoPDilNCQqUoCFARIfIeUq4FBY3_N6u8tfM,994
12
12
  seer_pas_sdk/objects/platemap.py,sha256=8IvJPAecs_e_FyqibzhCw-O4zjCFnf-zMUp_5krTEsg,5864
13
13
  seer_pas_sdk/objects/volcanoplot.py,sha256=tKuCWDIdoO8FLJlhpXhuwHn0aMYnvudTugxAslDXyGs,9357
14
- seer_pas_sdk-0.2.1.dist-info/licenses/LICENSE.txt,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
15
- seer_pas_sdk-0.2.1.dist-info/METADATA,sha256=qD8N0JVJ5AKlzsDy2N8d56rNoef0QFh8hvWAjx-rMJY,13421
16
- seer_pas_sdk-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- seer_pas_sdk-0.2.1.dist-info/top_level.txt,sha256=-2kZ-KFMGtXwr8H1O5llMKlcJ8gRKohEmrIvazXB61s,13
18
- seer_pas_sdk-0.2.1.dist-info/RECORD,,
14
+ seer_pas_sdk-0.3.0.dist-info/licenses/LICENSE.txt,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
15
+ seer_pas_sdk-0.3.0.dist-info/METADATA,sha256=dpaBy3hI3vwikAThyVZeWjF5qNyiHrJhwEVSbmJA9rU,13448
16
+ seer_pas_sdk-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ seer_pas_sdk-0.3.0.dist-info/top_level.txt,sha256=-2kZ-KFMGtXwr8H1O5llMKlcJ8gRKohEmrIvazXB61s,13
18
+ seer_pas_sdk-0.3.0.dist-info/RECORD,,