basepair 1.5__0-py3.8.egg
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.
- EGG-INFO/PKG-INFO +27 -0
- EGG-INFO/SOURCES.txt +38 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/requires.txt +5 -0
- EGG-INFO/scripts/basepair +637 -0
- EGG-INFO/top_level.txt +1 -0
- EGG-INFO/zip-safe +1 -0
- basepair/__init__.py +32 -0
- basepair/__pycache__/__init__.cpython-38.pyc +0 -0
- basepair/__pycache__/api.cpython-38.pyc +0 -0
- basepair/api.py +1353 -0
- basepair/helpers/__init__.py +3 -0
- basepair/helpers/__pycache__/__init__.cpython-38.pyc +0 -0
- basepair/helpers/__pycache__/eprint.cpython-38.pyc +0 -0
- basepair/helpers/__pycache__/nice_print.cpython-38.pyc +0 -0
- basepair/helpers/__pycache__/set_filter.cpython-38.pyc +0 -0
- basepair/helpers/eprint.py +8 -0
- basepair/helpers/nice_print.py +170 -0
- basepair/helpers/set_filter.py +18 -0
- basepair/infra/__init__.py +0 -0
- basepair/infra/__pycache__/__init__.cpython-38.pyc +0 -0
- basepair/infra/configuration/__init__.py +1 -0
- basepair/infra/configuration/__pycache__/__init__.cpython-38.pyc +0 -0
- basepair/infra/configuration/__pycache__/parser.cpython-38.pyc +0 -0
- basepair/infra/configuration/parser.py +67 -0
- basepair/infra/webapp/__init__.py +12 -0
- basepair/infra/webapp/__pycache__/__init__.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/abstract.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/analysis.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/file.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/gene.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/genome.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/genome_file.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/host.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/module.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/pipeline.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/project.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/sample.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/upload.cpython-38.pyc +0 -0
- basepair/infra/webapp/__pycache__/user.cpython-38.pyc +0 -0
- basepair/infra/webapp/abstract.py +128 -0
- basepair/infra/webapp/analysis.py +10 -0
- basepair/infra/webapp/file.py +10 -0
- basepair/infra/webapp/gene.py +10 -0
- basepair/infra/webapp/genome.py +10 -0
- basepair/infra/webapp/genome_file.py +10 -0
- basepair/infra/webapp/host.py +10 -0
- basepair/infra/webapp/module.py +10 -0
- basepair/infra/webapp/pipeline.py +10 -0
- basepair/infra/webapp/project.py +10 -0
- basepair/infra/webapp/sample.py +10 -0
- basepair/infra/webapp/upload.py +10 -0
- basepair/infra/webapp/user.py +48 -0
- basepair/utils/__init__.py +0 -0
- basepair/utils/__pycache__/__init__.cpython-38.pyc +0 -0
- basepair/utils/__pycache__/colors.cpython-38.pyc +0 -0
- basepair/utils/__pycache__/monitor.cpython-38.pyc +0 -0
- basepair/utils/__pycache__/s3.cpython-38.pyc +0 -0
- basepair/utils/__pycache__/sqs.cpython-38.pyc +0 -0
- basepair/utils/__pycache__/swf.cpython-38.pyc +0 -0
- basepair/utils/colors.py +43 -0
- basepair/utils/monitor.py +38 -0
- basepair/utils/s3.py +77 -0
- basepair/utils/sqs.py +147 -0
- basepair/utils/swf.py +129 -0
basepair/api.py
ADDED
|
@@ -0,0 +1,1353 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Python interface to Basepair's REST API
|
|
3
|
+
|
|
4
|
+
Copyright [2015] - [2018] Basepair INC
|
|
5
|
+
All Rights Reserved.
|
|
6
|
+
|
|
7
|
+
NOTICE: All information contained herein is, and remains
|
|
8
|
+
the property of Basepair INC and its suppliers, if any.
|
|
9
|
+
The intellectual and technical concepts contained
|
|
10
|
+
herein are proprietary to Basepair INC
|
|
11
|
+
and its suppliers and may be covered by U.S. and Foreign Patents,
|
|
12
|
+
patents in process, and are protected by trade secret or copyright law.
|
|
13
|
+
Dissemination of this information or reproduction of this material
|
|
14
|
+
is strictly forbidden unless prior written permission is obtained
|
|
15
|
+
from Basepair INC.
|
|
16
|
+
'''
|
|
17
|
+
|
|
18
|
+
# pylint: disable=too-many-lines
|
|
19
|
+
|
|
20
|
+
from __future__ import division
|
|
21
|
+
from __future__ import print_function
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
import json
|
|
26
|
+
import subprocess
|
|
27
|
+
from subprocess import CalledProcessError
|
|
28
|
+
import time
|
|
29
|
+
import datetime
|
|
30
|
+
|
|
31
|
+
# App imports
|
|
32
|
+
from .helpers import eprint, NicePrint, SetFilter
|
|
33
|
+
from .infra.configuration import Parser
|
|
34
|
+
from .infra.webapp import Analysis, File, Gene, Genome, GenomeFile, Host, Module, Pipeline, Project, Sample, Upload, User
|
|
35
|
+
|
|
36
|
+
class BpApi(): # pylint: disable=too-many-instance-attributes,too-many-public-methods
|
|
37
|
+
''' A wrapper over the REST API for accessing the Basepair system
|
|
38
|
+
|
|
39
|
+
Use it thus:
|
|
40
|
+
|
|
41
|
+
> import basepair
|
|
42
|
+
> bp = basepair.connect()
|
|
43
|
+
|
|
44
|
+
conf is a JSON object with at least these keys:
|
|
45
|
+
{
|
|
46
|
+
"api": {
|
|
47
|
+
"host": "app.basepairtech.com",
|
|
48
|
+
"prefix": "/api/v2/",
|
|
49
|
+
"username": "utk",
|
|
50
|
+
"key": "xxx"
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Some of the common methods are:
|
|
55
|
+
|
|
56
|
+
# create a sample
|
|
57
|
+
sample_id = bp.create_sample(info_dict)
|
|
58
|
+
|
|
59
|
+
# create an analysis
|
|
60
|
+
analysis_id = bp.create_analysis(workflow_id, sample_id)
|
|
61
|
+
|
|
62
|
+
# download results - bam, dedup file
|
|
63
|
+
filename = bp.get_file_by_tags(sample_id, ['bam', 'dedup'])
|
|
64
|
+
|
|
65
|
+
# delete sample
|
|
66
|
+
bp.delete_sample(sample_id)
|
|
67
|
+
'''
|
|
68
|
+
|
|
69
|
+
def __init__(self, conf=None, scratch='.', use_cache=False, user_cache_for_host_conf=False, verbose=None): # pylint: disable=too-many-arguments
|
|
70
|
+
self.verbose = verbose
|
|
71
|
+
|
|
72
|
+
if conf:
|
|
73
|
+
self.conf = conf
|
|
74
|
+
elif 'BP_CONFIG_FILE' in os.environ:
|
|
75
|
+
self.conf = json.load(open(os.environ['BP_CONFIG_FILE']))
|
|
76
|
+
else:
|
|
77
|
+
if 'BP_USERNAME' not in os.environ:
|
|
78
|
+
eprint('ERROR: BP_USERNAME not set in env')
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
if 'BP_API_KEY' not in os.environ:
|
|
81
|
+
eprint('ERROR: BP_API_KEY not set in env')
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
username = os.environ['BP_USERNAME']
|
|
84
|
+
api_key = os.environ['BP_API_KEY']
|
|
85
|
+
self.conf = {
|
|
86
|
+
'api': {
|
|
87
|
+
'key': api_key,
|
|
88
|
+
'host': 'app.basepairtech.com',
|
|
89
|
+
'prefix': '/api/v2/',
|
|
90
|
+
'ssl': True,
|
|
91
|
+
'username': username,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if self.conf.get('api', {}).get('ssl') is None:
|
|
96
|
+
eprint('ERROR: The config file need to be updated. Please visit:')
|
|
97
|
+
eprint('https://test.basepairtech.com/api/v2/users/api_key')
|
|
98
|
+
eprint('To get your new config file.')
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
self.scratch = self.conf.get('scratch', scratch).rstrip('/')
|
|
102
|
+
self.use_cache = use_cache
|
|
103
|
+
|
|
104
|
+
if use_cache and self.verbose == 1:
|
|
105
|
+
eprint('Warning: caching data.')
|
|
106
|
+
|
|
107
|
+
self._get_user_id()
|
|
108
|
+
|
|
109
|
+
self.genomes = self.get_genomes()
|
|
110
|
+
|
|
111
|
+
# Get configuration from host only if it is not set in the incoming config
|
|
112
|
+
cache = False
|
|
113
|
+
configuration = self.conf
|
|
114
|
+
if user_cache_for_host_conf:
|
|
115
|
+
eprint('INFO: Use cached host cloud service configuration.')
|
|
116
|
+
cache = '{}/json/config.json'.format(self.scratch)
|
|
117
|
+
|
|
118
|
+
if self.conf.get('api', {}).get('cli'):
|
|
119
|
+
configuration = (User(self.conf.get('api'))).get_configuration(cache=cache)
|
|
120
|
+
self.configuration = Parser(configuration)
|
|
121
|
+
|
|
122
|
+
self.DATATYPES = ['dna-seq', 'chip-seq', 'rna-seq', 'atac-seq', 'other'] # pylint: disable=invalid-name
|
|
123
|
+
self.LIST_TYPES = ['samples', 'analyses', 'genomes', 'workflows', 'analysis'] # pylint: disable=invalid-name
|
|
124
|
+
|
|
125
|
+
################################################################################################
|
|
126
|
+
### ANALYSIS ###################################################################################
|
|
127
|
+
################################################################################################
|
|
128
|
+
def create_analysis(
|
|
129
|
+
self,
|
|
130
|
+
workflow_id,
|
|
131
|
+
control_id=None,
|
|
132
|
+
control_ids=[],
|
|
133
|
+
ignore_validation_warnings=False,
|
|
134
|
+
params=None,
|
|
135
|
+
project_id=None,
|
|
136
|
+
sample_id=None,
|
|
137
|
+
sample_ids=[],
|
|
138
|
+
): # pylint: disable=dangerous-default-value,too-many-arguments
|
|
139
|
+
'''Create analysis
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
workflow_id: {str} Workflow id. Run bp.get_workflows() to see what is available. [Required]
|
|
143
|
+
|
|
144
|
+
control_id: {str} Control sample id.
|
|
145
|
+
control_ids: {list} Control sample id.
|
|
146
|
+
ignore_validation_warnings: {bool} Ignore validation warnings
|
|
147
|
+
params: {dict} Dictionary of parameter values.
|
|
148
|
+
project_id: {str} Project id.
|
|
149
|
+
sample_id: {str} Sample id.
|
|
150
|
+
sample_ids: {list} List of sample id.
|
|
151
|
+
'''
|
|
152
|
+
|
|
153
|
+
# get api version
|
|
154
|
+
prefix = self.conf.get('api', {}).get('prefix', '/api/v2/')
|
|
155
|
+
if control_id:
|
|
156
|
+
control_ids.append(control_id)
|
|
157
|
+
if sample_id:
|
|
158
|
+
sample_ids.append(sample_id)
|
|
159
|
+
|
|
160
|
+
# check if valid workflow id
|
|
161
|
+
if not self._check_workflow(workflow_id):
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
# check if all sample ids are valid
|
|
165
|
+
if not all([self._check_sample(item_id) for item_id in sample_ids]):
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
data = {
|
|
169
|
+
'controls': self._parsed_sample_list(control_ids, prefix),
|
|
170
|
+
'samples': self._parsed_sample_list(sample_ids, prefix),
|
|
171
|
+
'ignore_validation_warning': ignore_validation_warnings,
|
|
172
|
+
'workflow': '{}pipelines/{}'.format(prefix, workflow_id)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if project_id:
|
|
176
|
+
data['projects'] = ['{}projects/{}'.format(prefix, project_id)]
|
|
177
|
+
eprint(json.dumps(data, indent=2))
|
|
178
|
+
|
|
179
|
+
if params:
|
|
180
|
+
data['params'] = params
|
|
181
|
+
|
|
182
|
+
analysis_api = Analysis(self.conf.get('api'))
|
|
183
|
+
info = analysis_api.save(payload=data)
|
|
184
|
+
analysis_id = info.get('id')
|
|
185
|
+
if self.verbose and analysis_id:
|
|
186
|
+
eprint('created: analysis {} with sample id(s) {}'.format(
|
|
187
|
+
analysis_id,
|
|
188
|
+
','.join(sample_ids),
|
|
189
|
+
))
|
|
190
|
+
return analysis_id
|
|
191
|
+
|
|
192
|
+
def delete_analysis(self, uid):
|
|
193
|
+
'''Delete method'''
|
|
194
|
+
return (Analysis(self.conf.get('api'))).delete(uid)
|
|
195
|
+
|
|
196
|
+
def download_analysis(self, uid, outdir='.', tagkind=None, tags=None):
|
|
197
|
+
'''
|
|
198
|
+
Download files from one or more analysis.
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
uid: {int} One or more uids identifying the analysesself [Required]
|
|
202
|
+
outdir: {str} Output directory to download results to
|
|
203
|
+
tagkind: {str} Type of tag filtering to do. Options: exact, diff, subset
|
|
204
|
+
tags: {list} List of list of tags to filter files by
|
|
205
|
+
'''
|
|
206
|
+
if tagkind not in ['exact', 'diff', 'subset']:
|
|
207
|
+
eprint('Invalid tagkind, choose one of: exact, diff, subset')
|
|
208
|
+
return None
|
|
209
|
+
if tags is not None:
|
|
210
|
+
is_not_valid = not (isinstance(tags, list) and isinstance(tags[0], list))
|
|
211
|
+
if is_not_valid:
|
|
212
|
+
eprint('Invald tags argument. Provide a list of list of tags.')
|
|
213
|
+
return None
|
|
214
|
+
if not isinstance(uid, list):
|
|
215
|
+
uid = [uid]
|
|
216
|
+
for item_id in uid:
|
|
217
|
+
analysis = self.get_analysis(item_id)
|
|
218
|
+
self.get_analysis_files(
|
|
219
|
+
analysis=analysis,
|
|
220
|
+
dirname=outdir,
|
|
221
|
+
kind=tagkind,
|
|
222
|
+
tags=tags,
|
|
223
|
+
)
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
def fusionsalysis(
|
|
227
|
+
self,
|
|
228
|
+
workflow_id,
|
|
229
|
+
control_id=None,
|
|
230
|
+
control_ids=[],
|
|
231
|
+
params=None,
|
|
232
|
+
sample_id=None,
|
|
233
|
+
sample_ids=[]
|
|
234
|
+
): # pylint: disable=dangerous-default-value,too-many-arguments
|
|
235
|
+
'''Create analysis
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
workflow_id: {str} Workflow id. Run bp.get_workflows() to see what is available. [Required]
|
|
241
|
+
|
|
242
|
+
control_id: {str} Control sample id.
|
|
243
|
+
control_ids: {list} Control sample id.
|
|
244
|
+
params: {dict} Dictionary of parameter values.
|
|
245
|
+
sample_id: {str} Sample id.
|
|
246
|
+
sample_ids: {list} List of sample id.
|
|
247
|
+
'''
|
|
248
|
+
|
|
249
|
+
prefix = self.conf.get('api', {}).get('prefix', '/api/v2/')
|
|
250
|
+
if control_id:
|
|
251
|
+
control_ids.append(control_id)
|
|
252
|
+
if sample_id:
|
|
253
|
+
sample_ids.append(sample_id)
|
|
254
|
+
|
|
255
|
+
data = {
|
|
256
|
+
'controls': self._parsed_sample_list(control_ids, prefix),
|
|
257
|
+
'samples': self._parsed_sample_list(sample_ids, prefix),
|
|
258
|
+
'workflow': '{}pipelines/{}'.format(prefix, workflow_id),
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if params:
|
|
262
|
+
data['params'] = params
|
|
263
|
+
|
|
264
|
+
info = (Analysis(self.conf.get('api'))).save(payload=data)
|
|
265
|
+
if info.get('error'):
|
|
266
|
+
eprint('failed:', ','.join(sample_ids), info.get('msg'))
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
analysis_id = info.get('id')
|
|
270
|
+
if self.verbose and analysis_id:
|
|
271
|
+
eprint('created: analysis {} w/ sample {}'.format(analysis_id, ','.join(sample_ids)))
|
|
272
|
+
return analysis_id
|
|
273
|
+
|
|
274
|
+
def get_analysis(self, uid):
|
|
275
|
+
'''Get resource'''
|
|
276
|
+
return (Analysis(self.conf.get('api'))).get(
|
|
277
|
+
uid,
|
|
278
|
+
cache='{}/json/analysis.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def get_analysis_owner(self, analysis_id):
|
|
282
|
+
'''get owner user for analysis'''
|
|
283
|
+
user_id = self._get_analysis_owner_id(analysis_id)
|
|
284
|
+
return self.get_user(user_id) if user_id else None
|
|
285
|
+
|
|
286
|
+
def get_analyses(self):
|
|
287
|
+
'''Get resource list'''
|
|
288
|
+
return (Analysis(self.conf.get('api'))).list_all()
|
|
289
|
+
|
|
290
|
+
def update_analysis(self, uid, data):
|
|
291
|
+
'''Update resource'''
|
|
292
|
+
info = (Analysis(self.conf.get('api'))).save(
|
|
293
|
+
obj_id=uid,
|
|
294
|
+
payload=data,
|
|
295
|
+
)
|
|
296
|
+
if info.get('error'):
|
|
297
|
+
eprint('cudnt update analysis {}, msg {}'.format(uid, info.get('msg')))
|
|
298
|
+
if self.verbose:
|
|
299
|
+
eprint('analysis', uid, 'updated')
|
|
300
|
+
return info
|
|
301
|
+
|
|
302
|
+
################################################################################################
|
|
303
|
+
### FILE #######################################################################################
|
|
304
|
+
################################################################################################
|
|
305
|
+
def create_file(self, uid, data):
|
|
306
|
+
'''Create resource'''
|
|
307
|
+
return (File(self.conf.get('api'))).save(obj_id=uid, payload=data)
|
|
308
|
+
|
|
309
|
+
def get_file(self, uid):
|
|
310
|
+
'''Get resource'''
|
|
311
|
+
return (File(self.conf.get('api'))).get(
|
|
312
|
+
uid,
|
|
313
|
+
cache='{}/json/file.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def update_file(self, uid, data):
|
|
317
|
+
'''Update resource'''
|
|
318
|
+
return (File(self.conf.get('api'))).save(obj_id=uid, payload=data)
|
|
319
|
+
|
|
320
|
+
################################################################################################
|
|
321
|
+
### GENE #######################################################################################
|
|
322
|
+
################################################################################################
|
|
323
|
+
def create_gene(self, data):
|
|
324
|
+
'''Create resource'''
|
|
325
|
+
return (Gene(self.conf.get('api'))).save(payload=data)
|
|
326
|
+
|
|
327
|
+
def delete_gene(self, uid):
|
|
328
|
+
'''Delete method'''
|
|
329
|
+
return (Gene(self.conf.get('api'))).delete(uid)
|
|
330
|
+
|
|
331
|
+
def get_gene(self, uid):
|
|
332
|
+
'''Get resource'''
|
|
333
|
+
return (Gene(self.conf.get('api'))).get(
|
|
334
|
+
uid,
|
|
335
|
+
cache='{}/json/gene.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def get_genes(self):
|
|
339
|
+
'''Get resource list'''
|
|
340
|
+
info = (Gene(self.conf.get('api'))).list(
|
|
341
|
+
params={'limit': 0}
|
|
342
|
+
)
|
|
343
|
+
return info.get('objects', [])
|
|
344
|
+
|
|
345
|
+
def get_genes_by_info(self, genome=None, symbol=None, tx_id=None):
|
|
346
|
+
'''Get genes by info'''
|
|
347
|
+
params = {'limit': 0}
|
|
348
|
+
if genome:
|
|
349
|
+
params['genome__name'] = genome
|
|
350
|
+
if symbol:
|
|
351
|
+
params['symbol__iexact'] = symbol
|
|
352
|
+
if tx_id:
|
|
353
|
+
params['tx_id'] = tx_id
|
|
354
|
+
info = (Gene(self.conf.get('api'))).list(params)
|
|
355
|
+
return info.get('objects', [])
|
|
356
|
+
|
|
357
|
+
def update_gene(self, uid, data):
|
|
358
|
+
'''Update resource'''
|
|
359
|
+
info = (Gene(self.conf.get('api'))).save(obj_id=uid, payload=data)
|
|
360
|
+
if self.verbose and not info.get('error'):
|
|
361
|
+
eprint('gene', uid, 'updated')
|
|
362
|
+
return info
|
|
363
|
+
|
|
364
|
+
################################################################################################
|
|
365
|
+
### GENOME #####################################################################################
|
|
366
|
+
################################################################################################
|
|
367
|
+
def create_genome(self, data):
|
|
368
|
+
'''Create resource'''
|
|
369
|
+
return (Genome(self.conf.get('api'))).save(payload=data)
|
|
370
|
+
|
|
371
|
+
def delete_genome(self, uid):
|
|
372
|
+
'''Delete method'''
|
|
373
|
+
return (Genome(self.conf.get('api'))).delete(uid)
|
|
374
|
+
|
|
375
|
+
def get_genome(self, uid):
|
|
376
|
+
'''Get resource'''
|
|
377
|
+
return (Genome(self.conf.get('api'))).get(
|
|
378
|
+
uid,
|
|
379
|
+
cache='{}/json/genome.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def get_genomes(self):
|
|
383
|
+
'''Get resource list'''
|
|
384
|
+
return (Genome(self.conf.get('api'))).list_all()
|
|
385
|
+
|
|
386
|
+
def update_genome(self, uid, data):
|
|
387
|
+
'''Update resource'''
|
|
388
|
+
info = (Genome(self.conf.get('api'))).save(obj_id=uid, payload=data)
|
|
389
|
+
if self.verbose and not info.get('error'):
|
|
390
|
+
eprint('genome', uid, 'updated')
|
|
391
|
+
return info
|
|
392
|
+
|
|
393
|
+
################################################################################################
|
|
394
|
+
### GENOME FILE ################################################################################
|
|
395
|
+
################################################################################################
|
|
396
|
+
def get_genomefile_by_filters(self, filters=None):
|
|
397
|
+
'''Get genome files by filters'''
|
|
398
|
+
if not filters:
|
|
399
|
+
eprint('Filters required.')
|
|
400
|
+
return None
|
|
401
|
+
filters['limit'] = 0
|
|
402
|
+
info = (GenomeFile(self.conf.get('api'))).list(params=filters)
|
|
403
|
+
return info.get('objects', [])
|
|
404
|
+
|
|
405
|
+
################################################################################################
|
|
406
|
+
### HOST #######################################################################################
|
|
407
|
+
################################################################################################
|
|
408
|
+
def get_host_by_domain(self, domain):
|
|
409
|
+
'''Get host by domain'''
|
|
410
|
+
info = (Host(self.conf.get('api'))).list({
|
|
411
|
+
'domain__exact': domain,
|
|
412
|
+
'limit': 1,
|
|
413
|
+
})
|
|
414
|
+
return info.get('objects')[0] if info.get('objects') else None
|
|
415
|
+
|
|
416
|
+
################################################################################################
|
|
417
|
+
### MODULE #####################################################################################
|
|
418
|
+
################################################################################################
|
|
419
|
+
def get_module(self, uid):
|
|
420
|
+
'''Get resource'''
|
|
421
|
+
return (Module(self.conf.get('api'))).get(
|
|
422
|
+
uid,
|
|
423
|
+
cache='{}/json/module.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
################################################################################################
|
|
427
|
+
### PIPELINE / WORKFLOW ########################################################################
|
|
428
|
+
################################################################################################
|
|
429
|
+
def get_workflow(self, uid):
|
|
430
|
+
'''Get resource'''
|
|
431
|
+
return (Pipeline(self.conf.get('api'))).get(
|
|
432
|
+
uid,
|
|
433
|
+
cache='{}/json/workflow.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
def get_workflows(self):
|
|
437
|
+
'''Get resource list'''
|
|
438
|
+
return (Pipeline(self.conf.get('api'))).list_all()
|
|
439
|
+
|
|
440
|
+
################################################################################################
|
|
441
|
+
### PROJECT ####################################################################################
|
|
442
|
+
################################################################################################
|
|
443
|
+
def create_project(self, data):
|
|
444
|
+
'''Create resource'''
|
|
445
|
+
info = (Project(self.conf.get('api'))).save(payload=data)
|
|
446
|
+
if self.verbose == 2:
|
|
447
|
+
eprint('Creating project:')
|
|
448
|
+
eprint('Data:', data)
|
|
449
|
+
|
|
450
|
+
project_id = info.get('id', None)
|
|
451
|
+
if project_id: # success
|
|
452
|
+
if self.verbose:
|
|
453
|
+
eprint('created: project with id', project_id)
|
|
454
|
+
else: # failure
|
|
455
|
+
eprint('failed project creation:', data['name'], info.get('msg'))
|
|
456
|
+
return project_id
|
|
457
|
+
|
|
458
|
+
def update_project(self, uid, data, params=None):
|
|
459
|
+
'''Update resource'''
|
|
460
|
+
info = (Project(self.conf.get('api'))).save(
|
|
461
|
+
obj_id=uid,
|
|
462
|
+
payload=data,
|
|
463
|
+
params=params
|
|
464
|
+
)
|
|
465
|
+
if self.verbose and not info.get('error'):
|
|
466
|
+
eprint('project', uid, 'updated')
|
|
467
|
+
return info
|
|
468
|
+
|
|
469
|
+
################################################################################################
|
|
470
|
+
### SAMPLE #####################################################################################
|
|
471
|
+
################################################################################################
|
|
472
|
+
def create_sample(self, data, source='api', upload=True): # pylint: disable=too-many-branches,too-many-locals,too-many-statements
|
|
473
|
+
'''Create sample with the provided info
|
|
474
|
+
|
|
475
|
+
Parameters
|
|
476
|
+
----------
|
|
477
|
+
data : dict
|
|
478
|
+
Dictionary of sample information.
|
|
479
|
+
|
|
480
|
+
source : str
|
|
481
|
+
TODO
|
|
482
|
+
|
|
483
|
+
upload : bool
|
|
484
|
+
Whether to upload the sample to the server or not.
|
|
485
|
+
|
|
486
|
+
'''
|
|
487
|
+
|
|
488
|
+
# get api version
|
|
489
|
+
prefix = self.conf.get('api', {}).get('prefix', '/api/v2/')
|
|
490
|
+
|
|
491
|
+
# some input validation
|
|
492
|
+
data['genome'] = self._get_genome_by_name(data.get('genome'))
|
|
493
|
+
|
|
494
|
+
if data.get('default_workflow'):
|
|
495
|
+
if not self._check_workflow(data['default_workflow']):
|
|
496
|
+
del data['default_workflow']
|
|
497
|
+
eprint('Provided workflow is not valid.')
|
|
498
|
+
|
|
499
|
+
if 'filepaths1' in data and data['filepaths1'] is None:
|
|
500
|
+
if self.verbose:
|
|
501
|
+
eprint('filepaths1 is None.')
|
|
502
|
+
del data['filepaths1']
|
|
503
|
+
|
|
504
|
+
if 'filepaths2' in data and data['filepaths2'] is None:
|
|
505
|
+
if self.verbose:
|
|
506
|
+
eprint('filepaths2 is None.')
|
|
507
|
+
del data['filepaths2']
|
|
508
|
+
|
|
509
|
+
# if only one sample as str, but into list
|
|
510
|
+
if data.get('filepaths1') and isinstance(data['filepaths1'], str):
|
|
511
|
+
data['filepaths1'] = [data['filepaths1']]
|
|
512
|
+
|
|
513
|
+
if data.get('filepaths2') and isinstance(data['filepaths2'], str):
|
|
514
|
+
data['filepaths2'] = [data['filepaths2']]
|
|
515
|
+
|
|
516
|
+
if data.get('filepaths2') and not data.get('filepaths1'):
|
|
517
|
+
eprint('ERROR: Parameter filepaths1 cannot be empty if filepaths2 is specified.')
|
|
518
|
+
return None
|
|
519
|
+
|
|
520
|
+
# validate unique file between filepaths1 and filepaths2
|
|
521
|
+
does_repeast = data.get('filepaths2') \
|
|
522
|
+
and any(path in data.get('filepaths2') for path in data.get('filepaths1'))
|
|
523
|
+
if does_repeast:
|
|
524
|
+
eprint('ERROR: Same file cannot be use in filepaths1 and filepaths2.')
|
|
525
|
+
return None
|
|
526
|
+
|
|
527
|
+
if data.get('default_workflow'):
|
|
528
|
+
data['default_workflow'] = '{}pipelines/{}'.format(prefix, data['default_workflow'])
|
|
529
|
+
|
|
530
|
+
if data.get('projects'):
|
|
531
|
+
project = (Project(self.conf.get('api'))).get(data.get('projects'))
|
|
532
|
+
if project.get('error'):
|
|
533
|
+
eprint('ERROR: Looking for project. {}'.format(project.get('msg')))
|
|
534
|
+
return None
|
|
535
|
+
data['projects'] = [project.get('resource_uri')]
|
|
536
|
+
|
|
537
|
+
info = (Sample(self.conf.get('api'))).save(payload=data)
|
|
538
|
+
sample_id = info.get('id')
|
|
539
|
+
|
|
540
|
+
if self.verbose == 2:
|
|
541
|
+
eprint('Creating sample:')
|
|
542
|
+
eprint('Data:', data)
|
|
543
|
+
|
|
544
|
+
if sample_id: # success
|
|
545
|
+
if self.verbose:
|
|
546
|
+
eprint('created: sample with id', sample_id)
|
|
547
|
+
else: # failure
|
|
548
|
+
eprint('failed sample creation:', data['name'], info.get('msg'))
|
|
549
|
+
return None
|
|
550
|
+
|
|
551
|
+
# do the actual upload, update filepath
|
|
552
|
+
files_to_upload = []
|
|
553
|
+
for path in data.get('filepaths1', []):
|
|
554
|
+
path = os.path.abspath(os.path.expanduser(
|
|
555
|
+
os.path.expandvars(path)
|
|
556
|
+
))
|
|
557
|
+
for path in data.get('filepaths2', []):
|
|
558
|
+
path = os.path.abspath(os.path.expanduser(
|
|
559
|
+
os.path.expandvars(path)
|
|
560
|
+
))
|
|
561
|
+
|
|
562
|
+
# if a sample id exists, then upload the files
|
|
563
|
+
if self.verbose:
|
|
564
|
+
eprint('Sample id: {}'.format(sample_id))
|
|
565
|
+
|
|
566
|
+
order = 0
|
|
567
|
+
all_files = data.get('filepaths1', []) + data.get('filepaths2', [])
|
|
568
|
+
for filepath in all_files:
|
|
569
|
+
if self.verbose:
|
|
570
|
+
eprint('Creating upload {}'.format(filepath))
|
|
571
|
+
|
|
572
|
+
upload_id, filepath, key = self.create_upload(
|
|
573
|
+
sample_id,
|
|
574
|
+
filepath,
|
|
575
|
+
order,
|
|
576
|
+
is_paired_end=filepath in data.get('filepaths2', []),
|
|
577
|
+
source=source
|
|
578
|
+
)
|
|
579
|
+
files_to_upload.append([upload_id, filepath, key])
|
|
580
|
+
order += 1
|
|
581
|
+
|
|
582
|
+
if upload:
|
|
583
|
+
for upload_id, filepath, key in files_to_upload:
|
|
584
|
+
if self.verbose:
|
|
585
|
+
eprint('Uploading. upload_id: {}, filepath: {}, key: {}'.format(
|
|
586
|
+
upload_id,
|
|
587
|
+
filepath,
|
|
588
|
+
key
|
|
589
|
+
))
|
|
590
|
+
self.upload_file(upload_id, filepath, key)
|
|
591
|
+
return sample_id
|
|
592
|
+
|
|
593
|
+
def delete_sample(self, uid):
|
|
594
|
+
'''Delete method'''
|
|
595
|
+
info = (Sample(self.conf.get('api'))).delete(uid)
|
|
596
|
+
if info.get('error'):
|
|
597
|
+
eprint('error: deleting {}, msg {}'.format(uid, info.get('msg')))
|
|
598
|
+
if self.verbose:
|
|
599
|
+
eprint('deleted sample', uid)
|
|
600
|
+
return info
|
|
601
|
+
|
|
602
|
+
def get_sample(self, uid, add_analysis=True):
|
|
603
|
+
'''Get resource'''
|
|
604
|
+
info = (Sample(self.conf.get('api'))).get(
|
|
605
|
+
uid,
|
|
606
|
+
cache='{}/json/sample.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
607
|
+
)
|
|
608
|
+
if info and add_analysis:
|
|
609
|
+
info = self._add_full_analysis(info)
|
|
610
|
+
return info
|
|
611
|
+
|
|
612
|
+
def get_sample_owner(self, sample_id):
|
|
613
|
+
'''Get sample owner'''
|
|
614
|
+
user_id = self._get_sample_owner_id(sample_id)
|
|
615
|
+
return self.get_user(user_id) if user_id else None
|
|
616
|
+
|
|
617
|
+
def get_samples(self):
|
|
618
|
+
'''Get resource list'''
|
|
619
|
+
return (Sample(self.conf.get('api'))).list_all()
|
|
620
|
+
|
|
621
|
+
def sample_name_to_id(self, name):
|
|
622
|
+
'''Get sample id from name'''
|
|
623
|
+
response = (Sample(self.conf.get('api'))).list({'limit': 2, 'name': name})
|
|
624
|
+
items = response.get('objects')
|
|
625
|
+
if not items:
|
|
626
|
+
eprint('WARNING: no sample by name', name)
|
|
627
|
+
return None
|
|
628
|
+
if len(items) > 1:
|
|
629
|
+
eprint('WARNING: multiple sample by name', name)
|
|
630
|
+
return items[0].get('id')
|
|
631
|
+
|
|
632
|
+
def update_sample(self, uid, data):
|
|
633
|
+
'''Update resource'''
|
|
634
|
+
info = (Sample(self.conf.get('api'))).save(obj_id=uid, payload=data)
|
|
635
|
+
if self.verbose and not info.get('error'):
|
|
636
|
+
eprint('sample', uid, 'updated')
|
|
637
|
+
return info
|
|
638
|
+
|
|
639
|
+
################################################################################################
|
|
640
|
+
### UPLOAD #####################################################################################
|
|
641
|
+
################################################################################################
|
|
642
|
+
def create_upload(self, sample_id, filepath, order, is_paired_end, source='api', uri=None): # pylint: disable=too-many-arguments
|
|
643
|
+
'''Create a upload object and actually upload the file'''
|
|
644
|
+
prefix = self.conf.get('api', {}).get('prefix', '/api/v2/')
|
|
645
|
+
filesize = None
|
|
646
|
+
key = filepath
|
|
647
|
+
status = 'in_progress'
|
|
648
|
+
if filepath.startswith('ftp://') or uri:
|
|
649
|
+
status = 'completed'
|
|
650
|
+
else:
|
|
651
|
+
key = 'uploads/{}/{}/{}'.format(
|
|
652
|
+
self.conf['user']['id'],
|
|
653
|
+
sample_id,
|
|
654
|
+
os.path.basename(filepath)
|
|
655
|
+
)
|
|
656
|
+
filesize = os.stat(filepath).st_size if os.path.exists(filepath) else 0
|
|
657
|
+
|
|
658
|
+
# update record with location of uploaded file
|
|
659
|
+
info = (Upload(self.conf.get('api'))).save(payload={
|
|
660
|
+
'filesize': filesize,
|
|
661
|
+
'is_paired_end': is_paired_end,
|
|
662
|
+
'key': key,
|
|
663
|
+
'order': order,
|
|
664
|
+
'sample': '{}samples/{}'.format(prefix, sample_id),
|
|
665
|
+
'source': source,
|
|
666
|
+
'status': status,
|
|
667
|
+
'uri': uri,
|
|
668
|
+
# 'timetaken': seconds_to_upload,
|
|
669
|
+
})
|
|
670
|
+
upload_id = info.get('id')
|
|
671
|
+
if upload_id:
|
|
672
|
+
if self.verbose:
|
|
673
|
+
eprint('\tcreated: upload with id', upload_id)
|
|
674
|
+
return upload_id, filepath, key
|
|
675
|
+
return None, None, None
|
|
676
|
+
|
|
677
|
+
def delete_upload(self, uid):
|
|
678
|
+
'''Delete method'''
|
|
679
|
+
return (Upload(self.conf.get('api'))).delete(uid)
|
|
680
|
+
|
|
681
|
+
def get_upload(self, uid):
|
|
682
|
+
'''Get resource'''
|
|
683
|
+
return (Upload(self.conf.get('api'))).get(
|
|
684
|
+
uid,
|
|
685
|
+
cache='{}/json/upload.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
def get_uploads(self):
|
|
689
|
+
'''Get resource list'''
|
|
690
|
+
info = (Upload(self.conf.get('api'))).list(
|
|
691
|
+
params={'limit': 0}
|
|
692
|
+
)
|
|
693
|
+
return info.get('objects', [])
|
|
694
|
+
|
|
695
|
+
def update_upload(self, uid, data):
|
|
696
|
+
'''Update resource'''
|
|
697
|
+
return (Upload(self.conf.get('api'))).save(obj_id=uid, payload=data)
|
|
698
|
+
|
|
699
|
+
def upload_file(self, upload_id, filepath, key):
|
|
700
|
+
'''Upload file to S3 and update info'''
|
|
701
|
+
starttime = time.time()
|
|
702
|
+
self.copy_file(filepath, key, action='to')
|
|
703
|
+
seconds_to_upload = int(time.time() - starttime)
|
|
704
|
+
(Upload(self.conf.get('api'))).save(obj_id=upload_id, payload={
|
|
705
|
+
'filesize': os.stat(filepath).st_size,
|
|
706
|
+
'seq_length': 0,
|
|
707
|
+
'status': 'completed',
|
|
708
|
+
'timetaken': seconds_to_upload,
|
|
709
|
+
})
|
|
710
|
+
|
|
711
|
+
def upload_uri_to_id(self, uri):
|
|
712
|
+
'''Get upload from uri and return upload id'''
|
|
713
|
+
response = (Upload(self.conf.get('api'))).list({'limit': 2, 'uri': uri})
|
|
714
|
+
items = response.get('objects')
|
|
715
|
+
if not items:
|
|
716
|
+
eprint('WARNING: no upload by uri', uri)
|
|
717
|
+
return None
|
|
718
|
+
if len(items) > 1:
|
|
719
|
+
eprint('WARNING: multiple upload by uri', uri)
|
|
720
|
+
return items[0].get('id')
|
|
721
|
+
|
|
722
|
+
################################################################################################
|
|
723
|
+
### USER #######################################################################################
|
|
724
|
+
################################################################################################
|
|
725
|
+
def get_user(self, uid):
|
|
726
|
+
'''Get resource'''
|
|
727
|
+
return (User(self.conf.get('api'))).get(
|
|
728
|
+
uid,
|
|
729
|
+
cache='{}/json/user.{}.json'.format(self.scratch, uid) if self.use_cache else False,
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
################################################################################################
|
|
733
|
+
### GENERAL HELPERS AND METHODS ################################################################
|
|
734
|
+
################################################################################################
|
|
735
|
+
def copy_file(self, src, dest, action='to'):
|
|
736
|
+
'''Copy file'''
|
|
737
|
+
return self.copy_file_to_s3(src, dest) if action == 'to' \
|
|
738
|
+
else self.copy_file_from_s3(src, dest)
|
|
739
|
+
|
|
740
|
+
def copy_file_from_s3(self, src, dest):
|
|
741
|
+
'''
|
|
742
|
+
Low level function to copy a file from cloud to disk
|
|
743
|
+
Parameters
|
|
744
|
+
----------
|
|
745
|
+
src: {str} File path on AWS S3, not including the bucket [Required]
|
|
746
|
+
dest: {str} Directory or file path where file will be downloaded to [Required]
|
|
747
|
+
'''
|
|
748
|
+
storage_cfg = self.configuration.get_user_storage()
|
|
749
|
+
if not src.startswith('s3://'):
|
|
750
|
+
src = 's3://{}/{}'.format(storage_cfg.get('bucket'), src)
|
|
751
|
+
cmd = self.get_copy_cmd(src, dest)
|
|
752
|
+
try:
|
|
753
|
+
return subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
|
|
754
|
+
except subprocess.CalledProcessError as stderr:
|
|
755
|
+
eprint("Error downloading {}.".format(src))
|
|
756
|
+
eprint("Return code: {}".format(stderr.returncode))
|
|
757
|
+
eprint("Ouput: {}".format(stderr.output))
|
|
758
|
+
return False
|
|
759
|
+
|
|
760
|
+
def copy_file_to_s3(self, src, dest, params=None):
|
|
761
|
+
'''Low level function to copy a file to cloud from disk'''
|
|
762
|
+
storage_cfg = self.configuration.get_user_storage()
|
|
763
|
+
dest = 's3://{}/{}'.format(storage_cfg.get('bucket'), dest)
|
|
764
|
+
cmd = self.get_copy_cmd(src, dest, sse=True, params=params)
|
|
765
|
+
if self.verbose:
|
|
766
|
+
eprint('copying', src, 'to', dest)
|
|
767
|
+
try:
|
|
768
|
+
return subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
|
|
769
|
+
except CalledProcessError as error:
|
|
770
|
+
eprint('error in copy file to s3')
|
|
771
|
+
eprint(cmd)
|
|
772
|
+
eprint(error.output)
|
|
773
|
+
return None
|
|
774
|
+
|
|
775
|
+
def download_file(self, filekey, dirname=None, filename=None, is_json=False, load=False): # pylint: disable=too-many-arguments
|
|
776
|
+
'''
|
|
777
|
+
High level function, downloads to scratch dir and opens and
|
|
778
|
+
parses files to JSON if asked. Uses low level copy_file()
|
|
779
|
+
Parameters
|
|
780
|
+
----------
|
|
781
|
+
filekey: {str} The full file path on AWS S3 [Required]
|
|
782
|
+
dirname: {str} Directory to download the file to
|
|
783
|
+
filename: {str} Give the download file a new name
|
|
784
|
+
isjson: {bool} Whether the downloaded file is JSON format
|
|
785
|
+
load: {bool} Load the file after downloading
|
|
786
|
+
Returns
|
|
787
|
+
-------
|
|
788
|
+
Absolute filepath to downloaded file
|
|
789
|
+
'''
|
|
790
|
+
|
|
791
|
+
if not filename:
|
|
792
|
+
# don't renaming the file
|
|
793
|
+
# get the download directory
|
|
794
|
+
prefix = self.scratch
|
|
795
|
+
if dirname:
|
|
796
|
+
prefix = dirname if dirname.startswith('/') else os.path.join(self.scratch, dirname)
|
|
797
|
+
filepath = os.path.join(prefix, os.path.basename(filekey))
|
|
798
|
+
else:
|
|
799
|
+
# rename the download file
|
|
800
|
+
filepath = self.get_filepath(filename, dirname=dirname)
|
|
801
|
+
filepath = os.path.expanduser(filepath)
|
|
802
|
+
|
|
803
|
+
# if file not already there, download it
|
|
804
|
+
if not os.path.exists(filepath) or os.path.getsize(filepath) == 0:
|
|
805
|
+
if self.verbose:
|
|
806
|
+
eprint('downloading', filepath)
|
|
807
|
+
if not os.path.exists(os.path.dirname(filepath)):
|
|
808
|
+
os.makedirs(os.path.dirname(filepath))
|
|
809
|
+
self.copy_file(filekey, filepath, action='from')
|
|
810
|
+
elif self.verbose:
|
|
811
|
+
eprint('exists', filepath)
|
|
812
|
+
|
|
813
|
+
if load:
|
|
814
|
+
data = open(filepath, 'r').read().strip()
|
|
815
|
+
return json.loads(data) if is_json else data
|
|
816
|
+
return filepath
|
|
817
|
+
|
|
818
|
+
def download_raw_files(self, sample, outdir=None):
|
|
819
|
+
'''
|
|
820
|
+
Download raw data associated with a sample
|
|
821
|
+
Parameters
|
|
822
|
+
----------
|
|
823
|
+
sample: {dict} From calling bp.get_sample() [Required]
|
|
824
|
+
outdir: {str} Output directory to save files to
|
|
825
|
+
'''
|
|
826
|
+
uploads = sample['uploads']
|
|
827
|
+
files = [(upload.get('uri') or upload.get('key')) for upload in uploads]
|
|
828
|
+
files_downloaded = []
|
|
829
|
+
for file_i in files:
|
|
830
|
+
outdir = outdir if outdir else self.scratch
|
|
831
|
+
fileout = os.path.join(outdir, os.path.split(file_i)[1])
|
|
832
|
+
if os.path.exists(fileout):
|
|
833
|
+
eprint('Not downloading. File exists: {}'.format(fileout))
|
|
834
|
+
output = True
|
|
835
|
+
else:
|
|
836
|
+
output = self.copy_file_from_s3(file_i, outdir)
|
|
837
|
+
|
|
838
|
+
if output:
|
|
839
|
+
eprint('Downloaded {} to {}.'.format(os.path.split(file_i)[1], outdir))
|
|
840
|
+
files_downloaded.append(fileout)
|
|
841
|
+
return files_downloaded
|
|
842
|
+
|
|
843
|
+
@classmethod
|
|
844
|
+
def filter_files_by_tags(cls, files, tags, exclude=None, kind='exact', multiple=False): # pylint: disable=too-many-arguments
|
|
845
|
+
'''
|
|
846
|
+
Filter files that have all the tags. If exclude tags provided,
|
|
847
|
+
only return the files that don't have the exclude tags.append
|
|
848
|
+
In some cases, multiple files are expected, set it to true.
|
|
849
|
+
Parameters
|
|
850
|
+
---------
|
|
851
|
+
files: {dict} List dictionaries containing file information [Required]
|
|
852
|
+
tags: {list} List of tags [Required]
|
|
853
|
+
kind: {str} Type of tag filtering. Options: exact, subset
|
|
854
|
+
exclude: {list} List of tags to exclude
|
|
855
|
+
multiple: {bool} Whether to return all matching files or only one
|
|
856
|
+
Returns
|
|
857
|
+
-------
|
|
858
|
+
A list of files
|
|
859
|
+
'''
|
|
860
|
+
|
|
861
|
+
if tags is None:
|
|
862
|
+
return files
|
|
863
|
+
|
|
864
|
+
if not isinstance(tags, list):
|
|
865
|
+
eprint('Invalid tags argument. Provide a list of tags.')
|
|
866
|
+
return None
|
|
867
|
+
|
|
868
|
+
# filter for matches
|
|
869
|
+
files = [
|
|
870
|
+
file for file in files if file['tags'] and getattr(SetFilter, kind)(tags, file['tags'])
|
|
871
|
+
]
|
|
872
|
+
|
|
873
|
+
# exclude files by tag
|
|
874
|
+
if exclude:
|
|
875
|
+
files = [
|
|
876
|
+
file for file in files if file['tags'] and SetFilter.diff(file['tags'], exclude)
|
|
877
|
+
]
|
|
878
|
+
|
|
879
|
+
# check if no files left after filtering
|
|
880
|
+
if len(files) == 0:
|
|
881
|
+
eprint('No matching files after filtering by tags:', tags)
|
|
882
|
+
return None
|
|
883
|
+
|
|
884
|
+
# return files
|
|
885
|
+
if multiple:
|
|
886
|
+
return files
|
|
887
|
+
if len(files) > 1:
|
|
888
|
+
eprint('multiple matches for tags:', tags)
|
|
889
|
+
return files[0:1]
|
|
890
|
+
return files
|
|
891
|
+
|
|
892
|
+
def get_analysis_files(self, analysis, dirname=None, kind='exact', tags=None): # pylint: disable=too-many-arguments
|
|
893
|
+
'''
|
|
894
|
+
For a analysis, go through files and get that match the tags
|
|
895
|
+
Parameters
|
|
896
|
+
----------
|
|
897
|
+
analysis: {dict} The dictionary returned by bp.get_analysis() [Required]
|
|
898
|
+
dirname: {str} Directory to download files to
|
|
899
|
+
kind: {str} Type of tag filtering. Options: exact, subset
|
|
900
|
+
tags: {list} List of lists of tags to filter by
|
|
901
|
+
'''
|
|
902
|
+
# some input checking
|
|
903
|
+
if tags:
|
|
904
|
+
is_not_valid = not (isinstance(tags, list) and all([isinstance(item, list) for item in tags]))
|
|
905
|
+
if is_not_valid:
|
|
906
|
+
eprint('Invald tags argument. Provide a list of list of tags.')
|
|
907
|
+
return None
|
|
908
|
+
else:
|
|
909
|
+
tags = [None]
|
|
910
|
+
|
|
911
|
+
# filter the files
|
|
912
|
+
matching_files = []
|
|
913
|
+
for tags_sub in tags:
|
|
914
|
+
matching_files += self.filter_files_by_tags(
|
|
915
|
+
analysis['files'],
|
|
916
|
+
tags_sub,
|
|
917
|
+
kind=kind,
|
|
918
|
+
multiple=True,
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
return [self.download_file(
|
|
922
|
+
matching_file['path'],
|
|
923
|
+
dirname=dirname,
|
|
924
|
+
) for matching_file in matching_files] if matching_files else None
|
|
925
|
+
|
|
926
|
+
def get_bam_file(self, sample, tags=None, multiple=False):
|
|
927
|
+
'''Get bam file. If you want deduped bam file, call with tags = ['bam', 'dedup']'''
|
|
928
|
+
return self.get_file_by_tags(
|
|
929
|
+
sample,
|
|
930
|
+
analysis_tags=['alignment'],
|
|
931
|
+
multiple=multiple,
|
|
932
|
+
tags=tags if tags else ['bam'],
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
def get_bigwig_file(self, sample, multiple=False):
|
|
936
|
+
'''Get bigwig file - for ChIP-Seq'''
|
|
937
|
+
return self.get_file_by_tags(
|
|
938
|
+
sample,
|
|
939
|
+
analysis_tags=['alignment'],
|
|
940
|
+
kind='exact',
|
|
941
|
+
multiple=multiple,
|
|
942
|
+
tags=['bigwig'],
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
def get_copy_cmd(self, src, dest, sse=False, params=None):
|
|
946
|
+
'''
|
|
947
|
+
Create copy cmd based on AWS cli utility
|
|
948
|
+
Parameters
|
|
949
|
+
----------
|
|
950
|
+
src: {str} Path to file on AWS S3 [Required]
|
|
951
|
+
dest: {str} Destination directory or path where file will be downloaded to [Required]
|
|
952
|
+
sse: {bool} Whether to use sever side encryption
|
|
953
|
+
params: {dict} Other download paramters
|
|
954
|
+
'''
|
|
955
|
+
_params = ''
|
|
956
|
+
if sse:
|
|
957
|
+
_params = '--sse'
|
|
958
|
+
if params:
|
|
959
|
+
for arg, val in params.items():
|
|
960
|
+
_params += ' {} "{}"'.format(arg, val)
|
|
961
|
+
|
|
962
|
+
storage_cfg = self.configuration.get_user_storage()
|
|
963
|
+
credential = self.configuration.get_cli_credentials_from(storage_cfg)
|
|
964
|
+
return '{}aws s3 cp "{}" "{}" {}'.format(credential, src, dest, _params)
|
|
965
|
+
|
|
966
|
+
def get_expression_count_file(self, sample, features='transcripts', multiple=False):
|
|
967
|
+
'''Get expression count text file - for RNA-Seq'''
|
|
968
|
+
tags = ['expression_count', 'by_transcript', 'text']
|
|
969
|
+
if features == 'genes':
|
|
970
|
+
tags = ['expression_count', 'by_gene', 'text']
|
|
971
|
+
if self.verbose:
|
|
972
|
+
eprint('getting file w tags', tags)
|
|
973
|
+
return self.get_file_by_tags(
|
|
974
|
+
sample,
|
|
975
|
+
analysis_tags=['alignment'],
|
|
976
|
+
kind='exact',
|
|
977
|
+
multiple=multiple,
|
|
978
|
+
tags=tags,
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
def get_file_by_node(
|
|
982
|
+
self,
|
|
983
|
+
sample,
|
|
984
|
+
analysis_tags=None,
|
|
985
|
+
dest=None,
|
|
986
|
+
dirname=None,
|
|
987
|
+
multiple=False,
|
|
988
|
+
node=None,
|
|
989
|
+
): # pylint: disable=too-many-arguments,too-many-branches,too-many-locals
|
|
990
|
+
'''For a sample, go through analysis and get files that match the tags'''
|
|
991
|
+
matches = []
|
|
992
|
+
for analysis in sample['analyses_full']:
|
|
993
|
+
should_continue = analysis['status'] == 'error' or (analysis_tags and not (
|
|
994
|
+
analysis['tags'] and (set(analysis_tags) <= set(analysis['tags']))
|
|
995
|
+
))
|
|
996
|
+
if should_continue:
|
|
997
|
+
continue
|
|
998
|
+
|
|
999
|
+
matching_file = self._filter_files_by_node(
|
|
1000
|
+
analysis['files'], node, multiple=multiple)
|
|
1001
|
+
|
|
1002
|
+
if matching_file:
|
|
1003
|
+
if multiple:
|
|
1004
|
+
for file in matching_file:
|
|
1005
|
+
matches.append([analysis['id'], file])
|
|
1006
|
+
else:
|
|
1007
|
+
matches.append([analysis['id'], matching_file])
|
|
1008
|
+
|
|
1009
|
+
if len(matches) == 0:
|
|
1010
|
+
eprint('WARNING: no matching file for', node)
|
|
1011
|
+
return None
|
|
1012
|
+
|
|
1013
|
+
if len(matches) > 1:
|
|
1014
|
+
matches.sort(
|
|
1015
|
+
key=lambda item: datetime.datetime.strptime(item[1]['last_updated'], '%Y-%m-%dT%H:%M:%S.%f'),
|
|
1016
|
+
reverse=True,
|
|
1017
|
+
)
|
|
1018
|
+
if not multiple:
|
|
1019
|
+
eprint('WARNING: multiple matching file for', node)
|
|
1020
|
+
for file in matches:
|
|
1021
|
+
eprint('\t', file[1]['last_updated'], file[1]['path'])
|
|
1022
|
+
|
|
1023
|
+
if multiple:
|
|
1024
|
+
filepath = []
|
|
1025
|
+
for match in matches:
|
|
1026
|
+
file1 = match[1]
|
|
1027
|
+
path = self.download_file(file1['path'], dirname=dirname)
|
|
1028
|
+
filepath.append(path)
|
|
1029
|
+
else:
|
|
1030
|
+
file1 = matches[0][1]
|
|
1031
|
+
if dest:
|
|
1032
|
+
dirname = dest
|
|
1033
|
+
filepath = self.download_file(file1['path'], filename=dest, dirname=dirname)
|
|
1034
|
+
return filepath
|
|
1035
|
+
|
|
1036
|
+
def get_file_by_tags(
|
|
1037
|
+
self,
|
|
1038
|
+
sample,
|
|
1039
|
+
analysis_tags=None,
|
|
1040
|
+
dest=None,
|
|
1041
|
+
dirname=None,
|
|
1042
|
+
download=True,
|
|
1043
|
+
exclude=None,
|
|
1044
|
+
kind='exact',
|
|
1045
|
+
multiple=False,
|
|
1046
|
+
tags=None,
|
|
1047
|
+
#workflow_id=None,
|
|
1048
|
+
): # pylint: disable=too-many-arguments,too-many-branches,too-many-locals,too-many-statements
|
|
1049
|
+
'''
|
|
1050
|
+
For a sample, go through analysis and get files that match the tags.
|
|
1051
|
+
Parameters
|
|
1052
|
+
----------
|
|
1053
|
+
sample: {dict} Sample information [Required]
|
|
1054
|
+
analysis_tags: {list} Analysis tags to filter analyses when looking for files
|
|
1055
|
+
dest: {str} File name to save the file to
|
|
1056
|
+
dirname: {str} Ouput directory to save the file to
|
|
1057
|
+
download: {bool} Whether to download the file(s)
|
|
1058
|
+
exclude: {list} List of tags to exclude
|
|
1059
|
+
kind: {str} Type of tag filtering to do. Options: exact, diff, or subset
|
|
1060
|
+
multiple: {bool} Whether to return multiple files or just the first one
|
|
1061
|
+
tags: {list} List of list of tags for file filtering. If just list of tags, will convert to list of lists.
|
|
1062
|
+
workflow_id: {int} Workflow id to look for files in. NOT IMPLEMENTED YE
|
|
1063
|
+
'''
|
|
1064
|
+
# some error checking
|
|
1065
|
+
if tags is None:
|
|
1066
|
+
eprint("Tags is None")
|
|
1067
|
+
return None
|
|
1068
|
+
|
|
1069
|
+
# make sure tags is a list of lists
|
|
1070
|
+
if isinstance(tags, list):
|
|
1071
|
+
if not isinstance(tags[0], list):
|
|
1072
|
+
tags = [tags]
|
|
1073
|
+
|
|
1074
|
+
matches = []
|
|
1075
|
+
matching_file = []
|
|
1076
|
+
for analysis in sample['analyses_full']:
|
|
1077
|
+
if analysis['status'] == 'error':
|
|
1078
|
+
eprint('analysis ended in error, skipping.')
|
|
1079
|
+
continue
|
|
1080
|
+
|
|
1081
|
+
# dont even look if not the right type of analysis
|
|
1082
|
+
should_continue = analysis_tags and not(
|
|
1083
|
+
analysis['tags'] and set(analysis_tags) <= set(analysis['tags'])
|
|
1084
|
+
)
|
|
1085
|
+
if should_continue:
|
|
1086
|
+
continue
|
|
1087
|
+
|
|
1088
|
+
if analysis['params'] and 'info' in analysis['params']:
|
|
1089
|
+
if not analysis['params']['info'].get('genome_id'):
|
|
1090
|
+
eprint('Could not find genome for analysis {}'.format(analysis['id']))
|
|
1091
|
+
continue
|
|
1092
|
+
sample_genome_id = self.get_id_from_url(sample.get('genome', ''))
|
|
1093
|
+
if not sample_genome_id:
|
|
1094
|
+
eprint('Could not find genome for analysis {}'.format(analysis['id']))
|
|
1095
|
+
continue
|
|
1096
|
+
if int(analysis['params']['info']['genome_id']) != int(sample_genome_id):
|
|
1097
|
+
eprint(
|
|
1098
|
+
'analysis genome {}'.format(analysis['params']['info']['genome_id']),
|
|
1099
|
+
'different from sample genome {}.'.format(sample_genome_id),
|
|
1100
|
+
)
|
|
1101
|
+
continue
|
|
1102
|
+
|
|
1103
|
+
if self.verbose:
|
|
1104
|
+
eprint('looking at', analysis['id'], 'status', analysis['status'])
|
|
1105
|
+
for tags_sub in tags:
|
|
1106
|
+
filtered_files = self.filter_files_by_tags(
|
|
1107
|
+
analysis['files'],
|
|
1108
|
+
tags_sub,
|
|
1109
|
+
exclude=exclude,
|
|
1110
|
+
kind=kind,
|
|
1111
|
+
multiple=multiple,
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
if filtered_files:
|
|
1115
|
+
matching_file += filtered_files
|
|
1116
|
+
|
|
1117
|
+
if matching_file:
|
|
1118
|
+
if multiple:
|
|
1119
|
+
for file in matching_file:
|
|
1120
|
+
matches.append([analysis['id'], file])
|
|
1121
|
+
else:
|
|
1122
|
+
matches.append([analysis['id'], matching_file[0]])
|
|
1123
|
+
|
|
1124
|
+
if not matches:
|
|
1125
|
+
eprint('WARNING: no matching file for', tags)
|
|
1126
|
+
eprint('in analyses with ids', [analysis['id'] for analysis in sample['analyses_full']])
|
|
1127
|
+
eprint('for sample', sample['id'])
|
|
1128
|
+
return None
|
|
1129
|
+
|
|
1130
|
+
if len(matches) > 1:
|
|
1131
|
+
matches.sort(
|
|
1132
|
+
key=lambda item: datetime.datetime.strptime(item[1]['last_updated'], '%Y-%m-%dT%H:%M:%S.%f'),
|
|
1133
|
+
reverse=True,
|
|
1134
|
+
)
|
|
1135
|
+
if not multiple:
|
|
1136
|
+
eprint('WARNING: multiple matching file for', tags)
|
|
1137
|
+
for file in matches:
|
|
1138
|
+
eprint('\t', file[1]['last_updated'], file[1]['path'])
|
|
1139
|
+
|
|
1140
|
+
if multiple:
|
|
1141
|
+
filepath = []
|
|
1142
|
+
for match in matches:
|
|
1143
|
+
file1 = match[1]
|
|
1144
|
+
|
|
1145
|
+
if download:
|
|
1146
|
+
path = self.download_file(file1['path'], dirname=dirname)
|
|
1147
|
+
filepath.append(path)
|
|
1148
|
+
else:
|
|
1149
|
+
filepath.append(file1['path'])
|
|
1150
|
+
else:
|
|
1151
|
+
file1 = matches[0][1]
|
|
1152
|
+
if download:
|
|
1153
|
+
filepath = self.download_file(file1['path'], filename=dest, dirname=dirname)
|
|
1154
|
+
else:
|
|
1155
|
+
filepath = file1['path']
|
|
1156
|
+
eprint('else')
|
|
1157
|
+
return filepath
|
|
1158
|
+
|
|
1159
|
+
def get_filepath(self, filename, dirname=None):
|
|
1160
|
+
'''Use scratch and [dirname] to construct a full filepath'''
|
|
1161
|
+
# move along, nothin to do
|
|
1162
|
+
if filename.startswith('/'):
|
|
1163
|
+
filepath = filename
|
|
1164
|
+
else:
|
|
1165
|
+
if dirname:
|
|
1166
|
+
filename = '{}/{}'.format(dirname.rstrip('/'), filename)
|
|
1167
|
+
# adding dirname made it full
|
|
1168
|
+
filepath = filename if filename.startswith('/') else self.scratch + '/' + filename
|
|
1169
|
+
return filepath
|
|
1170
|
+
|
|
1171
|
+
def get_history(self, analysis_id, outdir=None):
|
|
1172
|
+
'''
|
|
1173
|
+
Get SWF history for the analysis
|
|
1174
|
+
Parameters
|
|
1175
|
+
----------
|
|
1176
|
+
analysis_id: {int} Analysis ID [Required]
|
|
1177
|
+
outdir: {str} Directory to save data. Use instead of scratch.
|
|
1178
|
+
'''
|
|
1179
|
+
user_id = self._get_analysis_owner_id(analysis_id)
|
|
1180
|
+
filekey = 'log/analyses/{}/{}/history.json'.format(user_id, analysis_id)
|
|
1181
|
+
filename = '{}/history/history.{}.json'.format(
|
|
1182
|
+
outdir if outdir else self.scratch,
|
|
1183
|
+
analysis_id
|
|
1184
|
+
)
|
|
1185
|
+
return self.download_file(filekey, filename, load=True, is_json=True)
|
|
1186
|
+
|
|
1187
|
+
def get_id_from_url(self, url):
|
|
1188
|
+
'''Parse URL to get the id'''
|
|
1189
|
+
return self.parse_url(url)['id']
|
|
1190
|
+
|
|
1191
|
+
def get_log(self, analysis_id, outdir=None):
|
|
1192
|
+
'''
|
|
1193
|
+
Get log file for the analysis
|
|
1194
|
+
Parameters
|
|
1195
|
+
----------
|
|
1196
|
+
analysis_id: {int} Analysis ID [Required]
|
|
1197
|
+
outdir: {str} Directory to save data. Use instead of scratch.
|
|
1198
|
+
'''
|
|
1199
|
+
user_id = self._get_analysis_owner_id(analysis_id)
|
|
1200
|
+
filekey = 'log/analyses/{}/{}/worker.log'.format(user_id, analysis_id)
|
|
1201
|
+
filename = '{}/worker.{}.log'.format(outdir if outdir else self.scratch, analysis_id)
|
|
1202
|
+
return self.download_file(filekey, filename, load=False, is_json=False)
|
|
1203
|
+
|
|
1204
|
+
def get_window_score_filename(self, sample, kind=None, flanking=None):
|
|
1205
|
+
'''Get window score filename'''
|
|
1206
|
+
suffix = 'score-{}-{}Kb'.format(kind, flanking / 1e3)
|
|
1207
|
+
suffix = suffix.replace('.0Kb', 'Kb')
|
|
1208
|
+
return '{}/score/{}.{}.dedup.{}.txt'.format(
|
|
1209
|
+
self.scratch,
|
|
1210
|
+
sample['slug'],
|
|
1211
|
+
sample['genome'],
|
|
1212
|
+
suffix,
|
|
1213
|
+
)
|
|
1214
|
+
|
|
1215
|
+
@classmethod
|
|
1216
|
+
def parse_url(cls, url):
|
|
1217
|
+
'''Parse URL to get the id, and other stuff'''
|
|
1218
|
+
parts = url.rsplit('/', 1)
|
|
1219
|
+
return {'id': parts[1]}
|
|
1220
|
+
|
|
1221
|
+
def print_data(self, data_type='', is_json=False, uid=None):
|
|
1222
|
+
'''
|
|
1223
|
+
Print data associated with genomes, samples, etc..
|
|
1224
|
+
Parameters
|
|
1225
|
+
----------
|
|
1226
|
+
data_type: {str} Type of data to print (e.g. workflows)
|
|
1227
|
+
is_json: {bool} By default, data is printed in a human-readable format
|
|
1228
|
+
uid: {list} One or more ids of the objects you want
|
|
1229
|
+
'''
|
|
1230
|
+
|
|
1231
|
+
if not isinstance(uid, list):
|
|
1232
|
+
uid = [uid]
|
|
1233
|
+
|
|
1234
|
+
detail_methods = {
|
|
1235
|
+
'analysis': 'get_analysis',
|
|
1236
|
+
'genome': 'get_genome',
|
|
1237
|
+
'sample': 'get_sample',
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
list_methods = {
|
|
1241
|
+
'analyses': 'get_analyses',
|
|
1242
|
+
'genomes': 'get_genomes',
|
|
1243
|
+
'samples': 'get_samples',
|
|
1244
|
+
'workflows': 'get_workflows',
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
# get the appropriate data
|
|
1248
|
+
data = []
|
|
1249
|
+
|
|
1250
|
+
# if it is a detail
|
|
1251
|
+
if data_type in detail_methods:
|
|
1252
|
+
if uid is None:
|
|
1253
|
+
eprint('Your uid is invalid: {}'.format(uid))
|
|
1254
|
+
return
|
|
1255
|
+
|
|
1256
|
+
for item_id in uid:
|
|
1257
|
+
data_tmp = getattr(self, list_methods.get(data_type))(item_id)
|
|
1258
|
+
if data_tmp.get('id'):
|
|
1259
|
+
data.append(data_tmp)
|
|
1260
|
+
else:
|
|
1261
|
+
eprint('{} with uid {} invalid.'.format(data_type.capitalize(), item_id))
|
|
1262
|
+
|
|
1263
|
+
# if it is a list
|
|
1264
|
+
if data_type in list_methods:
|
|
1265
|
+
data = getattr(self, list_methods.get(data_type))()
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
if not data:
|
|
1269
|
+
eprint('Nothing found for the parameters you gave.')
|
|
1270
|
+
return
|
|
1271
|
+
|
|
1272
|
+
# print the data as json
|
|
1273
|
+
if is_json:
|
|
1274
|
+
for item in data:
|
|
1275
|
+
eprint(item)
|
|
1276
|
+
eprint()
|
|
1277
|
+
return
|
|
1278
|
+
|
|
1279
|
+
# print the data human readable
|
|
1280
|
+
getattr(NicePrint, data_type)(data)
|
|
1281
|
+
|
|
1282
|
+
### Private methods ###
|
|
1283
|
+
def _add_full_analysis(self, sample):
|
|
1284
|
+
'''Add full analysis info to the sample'''
|
|
1285
|
+
analysis_ids = [self.parse_url(uri)['id'] for uri in sample['analyses']]
|
|
1286
|
+
analyses = [self.get_analysis(uid) for uid in analysis_ids]
|
|
1287
|
+
# remove null analyses, probably deleted or no ownership
|
|
1288
|
+
analyses = [analysis for analysis in analyses if not analysis.get('error')]
|
|
1289
|
+
sample['analyses_full'] = analyses
|
|
1290
|
+
return sample
|
|
1291
|
+
|
|
1292
|
+
def _check_sample(self, uid):
|
|
1293
|
+
'''Check if the sample is in the Basepair database'''
|
|
1294
|
+
info = self.get_sample(uid, add_analysis=False)
|
|
1295
|
+
if info.get('id'):
|
|
1296
|
+
return True
|
|
1297
|
+
eprint('The provided sample id: {id}, does not exist in Basepair.'.format(id=uid))
|
|
1298
|
+
return False
|
|
1299
|
+
|
|
1300
|
+
def _check_workflow(self, uid):
|
|
1301
|
+
'''Check if the workflow is in the Basepair database'''
|
|
1302
|
+
info = self.get_workflow(uid)
|
|
1303
|
+
if info.get('id'):
|
|
1304
|
+
return True
|
|
1305
|
+
eprint('The provided workflow id: {id}, does not exist in Basepair.'.format(id=uid))
|
|
1306
|
+
return False
|
|
1307
|
+
|
|
1308
|
+
@classmethod
|
|
1309
|
+
def _filter_files_by_node(cls, files, node, multiple=False):
|
|
1310
|
+
'''Filter files that are from the node.'''
|
|
1311
|
+
# In some cases, multiple files are expected, set it to true.
|
|
1312
|
+
files = [file for file in files if file['node'] == node]
|
|
1313
|
+
if multiple:
|
|
1314
|
+
return files
|
|
1315
|
+
if not files:
|
|
1316
|
+
return None
|
|
1317
|
+
if len(files) > 1:
|
|
1318
|
+
eprint('multiple matches for node:', node)
|
|
1319
|
+
return files[0]
|
|
1320
|
+
|
|
1321
|
+
def _get_analysis_owner_id(self, analysis_id):
|
|
1322
|
+
'''Get analysis owner id'''
|
|
1323
|
+
info = self.get_analysis(analysis_id)
|
|
1324
|
+
return self.parse_url(info['owner'])['id'] if info else None
|
|
1325
|
+
|
|
1326
|
+
def _get_genome_by_name(self, genome_name):
|
|
1327
|
+
'''Check if the genome is in the Basepair database'''
|
|
1328
|
+
if genome_name:
|
|
1329
|
+
available_genomes = [item for item in self.genomes if item.get('name') == genome_name]
|
|
1330
|
+
if not available_genomes:
|
|
1331
|
+
eprint(
|
|
1332
|
+
'The provided genome, {}, does not exist in Basepair. Proceeding anyway...'.format(genome_name)
|
|
1333
|
+
)
|
|
1334
|
+
return None
|
|
1335
|
+
return available_genomes[0].get('resource_uri')
|
|
1336
|
+
return None
|
|
1337
|
+
|
|
1338
|
+
def _get_sample_owner_id(self, sample_id):
|
|
1339
|
+
'''Get sample owner id'''
|
|
1340
|
+
info = self.get_sample(sample_id)
|
|
1341
|
+
return self.parse_url(info['owner'])['id'] if info else None
|
|
1342
|
+
|
|
1343
|
+
def _get_user_id(self):
|
|
1344
|
+
'''Get userid, username provided in conf'''
|
|
1345
|
+
user = (User(self.conf.get('api'))).list(
|
|
1346
|
+
params={'limit': 1, 'username': self.conf['api']['username']}
|
|
1347
|
+
)
|
|
1348
|
+
self.conf['user'] = user.get('objects', [None])[0]
|
|
1349
|
+
|
|
1350
|
+
@classmethod
|
|
1351
|
+
def _parsed_sample_list(cls, items, prefix):
|
|
1352
|
+
'''Parse sample id list into sample resurce uri list'''
|
|
1353
|
+
return ['{}samples/{}'.format(prefix, item_id) for item_id in items]
|