forked from qiita-spots/qiita
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstudy.py
1597 lines (1363 loc) · 54.6 KB
/
study.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
r"""
Study and StudyPerson objects (:mod:`qiita_db.study`)
=====================================================
.. currentmodule:: qiita_db.study
This module provides the implementation of the Study and StudyPerson classes.
The study class allows access to all basic information including name and
pmids associated with the study, as well as returning ids for the data,
sample template, owner, and shared users. It is the central hub for creating,
deleting, and accessing a study in the database.
Contacts are taken care of by the StudyPerson class. This holds the contact's
name, email, address, and phone of the various persons in a study, e.g. The PI
or lab contact.
Classes
-------
.. autosummary::
:toctree: generated/
Study
StudyPerson
"""
# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------
from collections import defaultdict
from copy import deepcopy
from itertools import chain
import warnings
from qiita_core.exceptions import IncompetentQiitaDeveloperError
from qiita_core.qiita_settings import qiita_config
import qiita_db as qdb
class Study(qdb.base.QiitaObject):
r"""Study object to access to the Qiita Study information
Attributes
----------
data_types
info
investigation
name
pmids
shared_with
sample_template
status
title
owner
autoloaded
Methods
-------
raw_data
preprocessed_data
processed_data
add_pmid
exists
has_access
share
unshare
Notes
-----
All setters raise QiitaDBStatusError if trying to change a public study.
You should not be doing that.
"""
_table = "study"
_portal_table = "study_portal"
# The following columns are considered not part of the study info
_non_info = frozenset(["email", "study_title", "ebi_study_accession",
"autoloaded"])
def _lock_non_sandbox(self):
"""Raises QiitaDBStatusError if study is non-sandboxed"""
if self.status != 'sandbox':
raise qdb.exceptions.QiitaDBStatusError(
"Illegal operation on non-sandbox study!")
@classmethod
def from_title(cls, title):
"""Instantiate Study from title
Parameters
----------
title : str
Tht title to search for
Returns
-------
Study
The study with the given title
Raises
------
QiitaDBUnknownIDError
If the title doesn't exist
"""
with qdb.sql_connection.TRN:
sql = """SELECT study_id
FROM qiita.{}
WHERE study_title = %s""".format(cls._table)
qdb.sql_connection.TRN.add(sql, [title])
sid = qdb.sql_connection.TRN.execute_fetchflatten()
if not sid:
raise qdb.exceptions.QiitaDBUnknownIDError(
cls._table, f'"{title}" does not exist')
return qdb.study.Study(sid[0])
@classmethod
def iter(cls):
"""Iterate over all studies in the database
Returns
-------
generator
Yields a `Study` object for each study in the database,
in order of ascending study_id
"""
with qdb.sql_connection.TRN:
sql = """SELECT study_id FROM qiita.{}
ORDER BY study_id""".format(cls._table)
qdb.sql_connection.TRN.add(sql)
ids = qdb.sql_connection.TRN.execute_fetchflatten()
for id_ in ids:
yield Study(id_)
@property
def status(self):
r"""The status is inferred by the status of its artifacts"""
with qdb.sql_connection.TRN:
# Get the status of all its artifacts
sql = """SELECT DISTINCT visibility
FROM qiita.visibility
JOIN qiita.artifact USING (visibility_id)
JOIN qiita.study_artifact USING (artifact_id)
WHERE study_id = %s and visibility_id NOT IN %s"""
qdb.sql_connection.TRN.add(
sql, [self._id, qdb.util.artifact_visibilities_to_skip()])
return qdb.util.infer_status(
qdb.sql_connection.TRN.execute_fetchindex())
@staticmethod
def all_data_types():
"""Returns list of all the data types available in the system
Returns
-------
list of str
All the data types available in the system
"""
with qdb.sql_connection.TRN:
sql = "SELECT DISTINCT data_type FROM qiita.data_type"
qdb.sql_connection.TRN.add(sql)
return qdb.sql_connection.TRN.execute_fetchflatten()
@classmethod
def get_ids_by_status(cls, status):
"""Returns study id for all Studies with given status
Parameters
----------
status : str
Status setting to search for
Returns
-------
set of qiita_db.study.Study
All studies in the database that match the given status
"""
with qdb.sql_connection.TRN:
sql = """SELECT DISTINCT study_id
FROM qiita.study_artifact
JOIN qiita.artifact USING (artifact_id)
JOIN qiita.visibility USING (visibility_id)
JOIN qiita.study_portal USING (study_id)
JOIN qiita.portal_type USING (portal_type_id)
WHERE visibility = %s AND portal = %s"""
qdb.sql_connection.TRN.add(sql, [status, qiita_config.portal])
sids = set(qdb.sql_connection.TRN.execute_fetchflatten())
# If status is sandbox, all the studies that are not present in the
# study_artifact table are also sandbox
if status == 'sandbox':
sql = """SELECT study_id
FROM qiita.study
JOIN qiita.study_portal USING (study_id)
JOIN qiita.portal_type USING (portal_type_id)
WHERE portal = %s AND study_id NOT IN (
SELECT study_id
FROM qiita.study_artifact)"""
qdb.sql_connection.TRN.add(sql, [qiita_config.portal])
sids = sids.union(
qdb.sql_connection.TRN.execute_fetchflatten())
return sids
@classmethod
def get_by_status(cls, status):
"""Returns study id for all Studies with given status
Parameters
----------
status : str
Status setting to search for
Returns
-------
set of qiita_db.study.Study
All studies in the database that match the given status
"""
return set(cls(sid) for sid in cls.get_ids_by_status(status))
@classmethod
def get_info(cls, study_ids=None, info_cols=None):
"""Returns study data for a set of study_ids
Parameters
----------
study_ids : list of ints, optional
Studies to get information for. Defauls to all studies
info_cols: list of str, optional
Information columns to retrieve. Defaults to all study data
Returns
-------
list of DictCursor
Table-like structure of metadata, one study per row. Can be
accessed as a list of dictionaries, keyed on column name.
"""
# The following tables are considered part of info
_info_cols = frozenset(chain(
qdb.util.get_table_cols('study'),
qdb.util.get_table_cols('study_status'),
qdb.util.get_table_cols('timeseries_type'),
# placeholder for table study_publication
['publications']))
if info_cols is None:
info_cols = _info_cols
elif not _info_cols.issuperset(info_cols):
warnings.warn("Non-info columns passed: %s" % ", ".join(
set(info_cols) - _info_cols))
search_cols = ",".join(sorted(_info_cols.intersection(info_cols)))
with qdb.sql_connection.TRN:
sql = """SELECT {0}
FROM qiita.study
LEFT JOIN (
SELECT study_id,
array_agg(row_to_json((publication, is_doi), true))
AS publications
FROM qiita.study_publication
GROUP BY study_id)
AS full_publications
USING (study_id)
JOIN qiita.timeseries_type USING (timeseries_type_id)
JOIN qiita.study_portal USING (study_id)
JOIN qiita.portal_type USING (portal_type_id)
WHERE portal = %s""".format(search_cols)
args = [qiita_config.portal]
if study_ids is not None:
sql = "{0} AND study_id IN %s".format(sql)
args.append(tuple(study_ids))
qdb.sql_connection.TRN.add(sql, args)
rows = qdb.sql_connection.TRN.execute_fetchindex()
if study_ids is not None and len(rows) != len(study_ids):
raise qdb.exceptions.QiitaDBError(
'Non-portal-accessible studies asked for!')
res = []
for r in rows:
r = dict(r)
if 'ebi_study_accession' in info_cols:
r['ebi_submission_status'] = cls(
r['study_id']).ebi_submission_status
res.append(r)
return res
@classmethod
def exists(cls, study_title):
"""Check if a study exists based on study_title, which is unique
Parameters
----------
study_title : str
The title of the study to search for in the database
Returns
-------
bool
"""
with qdb.sql_connection.TRN:
sql = """SELECT EXISTS(
SELECT study_id
FROM qiita.{}
WHERE study_title = %s)""".format(cls._table)
qdb.sql_connection.TRN.add(sql, [study_title])
return qdb.sql_connection.TRN.execute_fetchlast()
@classmethod
def create(cls, owner, title, info, investigation=None):
"""Creates a new study on the database
Parameters
----------
owner : User object
the study's owner
title : str
Title of the study
info : dict
the information attached to the study. All "*_id" keys must pass
the objects associated with them.
investigation : Investigation object, optional
If passed, the investigation to associate with. Defaults to None.
Raises
------
QiitaDBColumnError
Non-db columns in info dictionary
All required keys not passed
IncompetentQiitaDeveloperError
email, study_id, study_status_id, or study_title passed as a key
QiitaDBDuplicateError
If a study with the given title already exists
Notes
-----
All keys in info, must be equal to columns in qiita.study table in the
database.
"""
# make sure not passing non-info columns in the info dict
if cls._non_info.intersection(info):
raise qdb.exceptions.QiitaDBColumnError(
"non info keys passed: %s" % cls._non_info.intersection(info))
# cleaning up title, this is also done in JS for the GUI but rather
# be safe than sorry
title = ' '.join(title.split()).strip()
with qdb.sql_connection.TRN:
if cls.exists(title):
raise qdb.exceptions.QiitaDBDuplicateError(
"Study", "title: %s" % title)
# add default values to info
insertdict = deepcopy(info)
insertdict['email'] = owner.id
insertdict['study_title'] = title
if "reprocess" not in insertdict:
insertdict['reprocess'] = False
# No nuns allowed
insertdict = {k: v for k, v in insertdict.items()
if v is not None}
# make sure dictionary only has keys for available columns in db
qdb.util.check_table_cols(insertdict, cls._table)
# make sure reqired columns in dictionary
qdb.util.check_required_columns(insertdict, cls._table)
# Insert study into database
sql = """INSERT INTO qiita.{0} ({1})
VALUES ({2}) RETURNING study_id""".format(
cls._table, ','.join(insertdict),
','.join(['%s'] * len(insertdict)))
# make sure data in same order as sql column names,
# and ids are used
data = []
for col in insertdict:
if isinstance(insertdict[col], qdb.base.QiitaObject):
data.append(insertdict[col].id)
else:
data.append(insertdict[col])
qdb.sql_connection.TRN.add(sql, data)
study_id = qdb.sql_connection.TRN.execute_fetchlast()
# Add to both QIITA and given portal (if not QIITA)
portal_id = qdb.util.convert_to_id(
qiita_config.portal, 'portal_type', 'portal')
sql = """INSERT INTO qiita.study_portal (study_id, portal_type_id)
VALUES (%s, %s)"""
args = [[study_id, portal_id]]
if qiita_config.portal != 'QIITA':
qp_id = qdb.util.convert_to_id(
'QIITA', 'portal_type', 'portal')
args.append([study_id, qp_id])
qdb.sql_connection.TRN.add(sql, args, many=True)
qdb.sql_connection.TRN.execute()
# add study to investigation if necessary
if investigation:
sql = """INSERT INTO qiita.investigation_study
(investigation_id, study_id)
VALUES (%s, %s)"""
qdb.sql_connection.TRN.add(sql, [investigation.id, study_id])
qdb.sql_connection.TRN.execute()
return cls(study_id)
@classmethod
def delete(cls, id_):
r"""Deletes the study from the database
Parameters
----------
id_ : integer
The object identifier
Raises
------
QiitaDBError
If the sample_(id_) table exists means a sample template exists
"""
with qdb.sql_connection.TRN:
# checking that the id_ exists
cls(id_)
if qdb.util.exists_table('sample_%d' % id_):
raise qdb.exceptions.QiitaDBError(
'Study "%s" cannot be erased because it has a '
'sample template' % cls(id_).title)
args = [id_]
sql = "DELETE FROM qiita.study_portal WHERE study_id = %s"
qdb.sql_connection.TRN.add(sql, args)
sql = "DELETE FROM qiita.study_publication WHERE study_id = %s"
qdb.sql_connection.TRN.add(sql, args)
sql = """DELETE FROM qiita.study_environmental_package
WHERE study_id = %s"""
qdb.sql_connection.TRN.add(sql, args)
sql = "DELETE FROM qiita.study_users WHERE study_id = %s"
qdb.sql_connection.TRN.add(sql, args)
sql = "DELETE FROM qiita.investigation_study WHERE study_id = %s"
qdb.sql_connection.TRN.add(sql, args)
sql = "DELETE FROM qiita.per_study_tags WHERE study_id = %s"
qdb.sql_connection.TRN.add(sql, args)
sql = "DELETE FROM qiita.study WHERE study_id = %s"
qdb.sql_connection.TRN.add(sql, args)
qdb.sql_connection.TRN.execute()
@classmethod
def get_tags(cls):
"""Returns the available study tags
Returns
-------
list of DictCursor
Table-like structure of metadata, one tag per row. Can be
accessed as a list of dictionaries, keyed on column name.
"""
with qdb.sql_connection.TRN:
sql = """SELECT qiita.user_level.name AS user_level,
array_agg(study_tag ORDER BY study_tag)
FROM qiita.study_tags
LEFT JOIN qiita.qiita_user USING (email)
LEFT JOIN qiita.user_level USING (user_level_id)
GROUP BY qiita.user_level.name"""
qdb.sql_connection.TRN.add(sql)
results = dict(qdb.sql_connection.TRN.execute_fetchindex())
# when the system is empty,
# it's possible to get an empty dict, fixing
if 'admin' not in results:
results['admin'] = []
if 'user' not in results:
results['user'] = []
return results
@classmethod
def insert_tags(cls, user, tags):
"""Insert available study tags
Parameters
----------
user : qiita_db.user.User
The user adding the tags
tags : list of str
The list of tags to add
"""
with qdb.sql_connection.TRN:
email = user.email
sql = """INSERT INTO qiita.study_tags (email, study_tag)
SELECT %s, %s WHERE NOT EXISTS (
SELECT 1 FROM qiita.study_tags WHERE study_tag = %s)"""
sql_args = [[email, tag, tag] for tag in tags]
qdb.sql_connection.TRN.add(sql, sql_args, many=True)
qdb.sql_connection.TRN.execute()
# --- Attributes ---
@property
def autoloaded(self):
"""Returns if the study was autoloaded
Returns
-------
bool
If the study was autoloaded or not
"""
with qdb.sql_connection.TRN:
sql = """SELECT autoloaded FROM qiita.{0}
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchlast()
@autoloaded.setter
def autoloaded(self, value):
"""Sets the autoloaded status of the study
Parameters
----------
value : bool
Whether the study was autoloaded
"""
sql = """UPDATE qiita.{0} SET autoloaded = %s
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.perform_as_transaction(sql, [value, self._id])
@property
def title(self):
"""Returns the title of the study
Returns
-------
str
Title of study
"""
with qdb.sql_connection.TRN:
sql = """SELECT study_title FROM qiita.{0}
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchlast()
@title.setter
def title(self, title):
"""Sets the title of the study
Parameters
----------
title : str
The study title
"""
sql = """UPDATE qiita.{0} SET study_title = %s
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.perform_as_transaction(sql, [title, self._id])
@property
def notes(self):
"""Returns the notes of the study
Returns
-------
str
Study notes
"""
with qdb.sql_connection.TRN:
sql = """SELECT notes FROM qiita.{0}
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchlast()
@notes.setter
def notes(self, notes):
"""Sets the notes of the study
Parameters
----------
notes : str
The study notes
"""
sql = """UPDATE qiita.{0} SET notes = %s
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.perform_as_transaction(sql, [notes, self._id])
@property
def public_raw_download(self):
"""Returns if the study's raw data is available for download
Returns
-------
str
public_raw_download of study
"""
with qdb.sql_connection.TRN:
sql = """SELECT public_raw_download FROM qiita.{0}
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchlast()
@public_raw_download.setter
def public_raw_download(self, public_raw_download):
"""Sets if the study's raw data is available for download
Parameters
----------
public_raw_download : bool
The study public_raw_download
"""
sql = """UPDATE qiita.{0} SET public_raw_download = %s
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.perform_as_transaction(
sql, [public_raw_download, self._id])
@property
def info(self):
"""Dict with all information attached to the study
Returns
-------
dict
info of study keyed to column names
"""
with qdb.sql_connection.TRN:
sql = "SELECT * FROM qiita.{0} WHERE study_id = %s".format(
self._table)
qdb.sql_connection.TRN.add(sql, [self._id])
info = dict(qdb.sql_connection.TRN.execute_fetchindex()[0])
# remove non-info items from info
for item in self._non_info:
info.pop(item)
# removed because redundant to the id already stored in the object
info.pop('study_id')
if info['principal_investigator_id']:
info['principal_investigator'] = qdb.study.StudyPerson(
info["principal_investigator_id"])
else:
info['principal_investigator'] = None
del info['principal_investigator_id']
if info['lab_person_id']:
info['lab_person'] = qdb.study.StudyPerson(
info["lab_person_id"])
else:
info['lab_person'] = None
del info['lab_person_id']
return info
@info.setter
def info(self, info):
"""Updates the information attached to the study
Parameters
----------
info : dict
information to change/update for the study, keyed to column name
Raises
------
IncompetentQiitaDeveloperError
Empty dict passed
QiitaDBColumnError
Unknown column names passed
"""
if not info:
raise IncompetentQiitaDeveloperError("Need entries in info dict!")
if 'study_id' in info:
raise qdb.exceptions.QiitaDBColumnError("Cannot set study_id!")
if self._non_info.intersection(info):
raise qdb.exceptions.QiitaDBColumnError(
"non info keys passed: %s" % self._non_info.intersection(info))
with qdb.sql_connection.TRN:
if 'timeseries_type_id' in info:
# We only lock if the timeseries type changes
self._lock_non_sandbox()
# make sure dictionary only has keys for available columns in db
qdb.util.check_table_cols(info, self._table)
sql_vals = []
data = []
# build query with data values in correct order for SQL statement
for key, val in info.items():
sql_vals.append("{0} = %s".format(key))
if isinstance(val, qdb.base.QiitaObject):
data.append(val.id)
else:
data.append(val)
data.append(self._id)
sql = "UPDATE qiita.{0} SET {1} WHERE study_id = %s".format(
self._table, ','.join(sql_vals))
qdb.sql_connection.TRN.add(sql, data)
qdb.sql_connection.TRN.execute()
@property
def shared_with(self):
"""list of users the study is shared with
Returns
-------
list of qiita_db.user.User
Users the study is shared with
"""
with qdb.sql_connection.TRN:
sql = """SELECT email FROM qiita.{0}_users
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.TRN.add(sql, [self._id])
return [qdb.user.User(uid)
for uid in qdb.sql_connection.TRN.execute_fetchflatten()]
@property
def publications(self):
""" Returns list of publications from this study
Returns
-------
list of (str, str)
list of all the DOI and pubmed ids
"""
with qdb.sql_connection.TRN:
sql = """SELECT publication, is_doi
FROM qiita.study_publication
WHERE study_id = %s"""
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchindex()
@publications.setter
def publications(self, values):
"""Sets the pmids for the study
Parameters
----------
values : list of (str, str)
The list of (DOI, pubmed id) to associate with the study
Raises
------
TypeError
If values is not a list
"""
# Check that a list is actually passed
if not isinstance(values, list):
raise TypeError('publications should be a list')
with qdb.sql_connection.TRN:
# Delete the previous pmids associated with the study
sql = "DELETE FROM qiita.study_publication WHERE study_id = %s"
qdb.sql_connection.TRN.add(sql, [self._id])
# Set the new ones
sql = """INSERT INTO qiita.study_publication
(study_id, publication, is_doi)
VALUES (%s, %s, %s)"""
sql_args = [[self._id, pub, is_doi] for pub, is_doi in values]
qdb.sql_connection.TRN.add(sql, sql_args, many=True)
qdb.sql_connection.TRN.execute()
@property
def investigation(self):
""" Returns Investigation this study is part of
If the study doesn't have an investigation associated with it, it will
return None
Returns
-------
qiita_db.investigation.Investigation or None
"""
with qdb.sql_connection.TRN:
sql = """SELECT investigation_id FROM qiita.investigation_study
WHERE study_id = %s"""
qdb.sql_connection.TRN.add(sql, [self._id])
inv = qdb.sql_connection.TRN.execute_fetchindex()
# If this study belongs to an investigation it will be in
# the first value of the first row [0][0]
return qdb.investigation.Investigation(inv[0][0]) if inv else None
@property
def sample_template(self):
"""Returns sample_template information
If the study doesn't have a sample template associated with it, it will
return None
Returns
-------
qiita_db.metadata_template.sample_template.SampleTemplate or None
"""
with qdb.sql_connection.TRN:
sql = """SELECT EXISTS(SELECT *
FROM qiita.study_sample
WHERE study_id = %s)"""
qdb.sql_connection.TRN.add(sql, [self.id])
exists = qdb.sql_connection.TRN.execute_fetchlast()
return (qdb.metadata_template.sample_template.SampleTemplate(self._id)
if exists else None)
@property
def data_types(self):
"""Returns list of the data types for this study
Returns
-------
list of str
"""
with qdb.sql_connection.TRN:
sql = """SELECT DISTINCT data_type
FROM qiita.study_prep_template
JOIN qiita.prep_template USING (prep_template_id)
JOIN qiita.data_type USING (data_type_id)
WHERE study_id = %s"""
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchflatten()
@property
def owner(self):
"""Gets the owner of the study
Returns
-------
qiita_db.user.User
The user that owns this study
"""
with qdb.sql_connection.TRN:
sql = """SELECT email FROM qiita.{} WHERE study_id = %s""".format(
self._table)
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.user.User(qdb.sql_connection.TRN.execute_fetchlast())
@property
def environmental_packages(self):
"""Gets the environmental packages associated with the study
Returns
-------
list of str
The environmental package names associated with the study
"""
with qdb.sql_connection.TRN:
sql = """SELECT environmental_package_name
FROM qiita.study_environmental_package
WHERE study_id = %s"""
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchflatten()
@environmental_packages.setter
def environmental_packages(self, values):
"""Sets the environmental packages for the study
Parameters
----------
values : list of str
The list of environmental package names to associate with the study
Raises
------
TypeError
If values is not a list
ValueError
If any environmental packages listed on values is not recognized
"""
with qdb.sql_connection.TRN:
# The environmental packages can be changed only if the study is
# sandboxed
self._lock_non_sandbox()
# Check that a list is actually passed
if not isinstance(values, list):
raise TypeError('Environmental packages should be a list')
# Get all the environmental packages
env_pkgs = [pkg[0]
for pkg in qdb.util.get_environmental_packages()]
# Check that all the passed values are valid environmental packages
missing = set(values).difference(env_pkgs)
if missing:
raise ValueError('Environmetal package(s) not recognized: %s'
% ', '.join(missing))
# Delete the previous environmental packages associated with
# the study
sql = """DELETE FROM qiita.study_environmental_package
WHERE study_id=%s"""
qdb.sql_connection.TRN.add(sql, [self._id])
# Set the new ones
sql = """INSERT INTO qiita.study_environmental_package
(study_id, environmental_package_name)
VALUES (%s, %s)"""
sql_args = [[self._id, val] for val in values]
qdb.sql_connection.TRN.add(sql, sql_args, many=True)
qdb.sql_connection.TRN.execute()
@property
def _portals(self):
"""Portals this study is associated with
Returns
-------
list of str
Portal names study is associated with
"""
with qdb.sql_connection.TRN:
sql = """SELECT portal
FROM qiita.portal_type
JOIN qiita.study_portal USING (portal_type_id)
WHERE study_id = %s"""
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchflatten()
@property
def ebi_study_accession(self):
"""The EBI study accession for this study
Returns
-------
str
The study EBI accession
"""
with qdb.sql_connection.TRN:
sql = """SELECT ebi_study_accession
FROM qiita.{0}
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.TRN.add(sql, [self._id])
return qdb.sql_connection.TRN.execute_fetchlast()
@ebi_study_accession.setter
def ebi_study_accession(self, value):
"""Sets the study's EBI study accession
Parameters
----------
value : str
The new EBI study accession
Raises
------
QiitDBError
If the study already has an EBI study accession
"""
if self.ebi_study_accession is not None:
raise qdb.exceptions.QiitaDBError(
"Study %s already has an EBI study accession"
% self.id)
sql = """UPDATE qiita.{}
SET ebi_study_accession = %s
WHERE study_id = %s""".format(self._table)
qdb.sql_connection.perform_as_transaction(sql, [value, self.id])
def _ebi_submission_jobs(self):
"""Helper code to avoid duplication"""
plugin = qdb.software.Software.from_name_and_version(
'Qiita', 'alpha')
cmd = plugin.get_command('submit_to_EBI')
sql = """SELECT processing_job_id,
pj.command_parameters->>'artifact' as aid,
processing_job_status, can_be_submitted_to_ebi,
array_agg(ebi_run_accession)
FROM qiita.processing_job pj
LEFT JOIN qiita.processing_job_status
USING (processing_job_status_id)
LEFT JOIN qiita.artifact ON (
artifact_id = (
pj.command_parameters->>'artifact')::INT)
LEFT JOIN qiita.ebi_run_accession era USING (artifact_id)
LEFT JOIN qiita.artifact_type USING (artifact_type_id)
WHERE pj.command_parameters->>'artifact' IN (
SELECT artifact_id::text
FROM qiita.study_artifact WHERE study_id = {0})
AND pj.command_id = {1}
GROUP BY processing_job_id, aid, processing_job_status,
can_be_submitted_to_ebi""".format(self._id, cmd.id)
qdb.sql_connection.TRN.add(sql)