Skip to content

Commit 7788f8f

Browse files
authored
fix #2320 (#3031)
* fix #2320 * adding prints to debug * children -> 1 * APIArtifactHandlerTest -> APIArtifactHandlerTests * configure_biom * qdb.util.activate_or_update_plugins * improving code * almost there * add values.template * fix filepaths * filepaths -> files * fixing errors * add prep.artifact insertion * addressing @ElDeveloper comments
1 parent d41102b commit 7788f8f

File tree

5 files changed

+200
-17
lines changed

5 files changed

+200
-17
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ install:
6464
- pip install https://github.com/qiita-spots/qiita_client/archive/master.zip
6565
- pip install https://github.com/qiita-spots/qtp-biom/archive/master.zip
6666
- export QIITA_SERVER_CERT=`pwd`/qiita_core/support_files/server.crt
67-
- configure_biom --env-script "source ~/virtualenv/python2.7/bin/activate; export PATH=$HOME/miniconda3/bin/:$PATH; . activate qtp-biom" --server-cert $QIITA_SERVER_CERT
67+
- configure_biom --env-script "export PATH=$HOME/miniconda3/bin/:$PATH; source activate qtp-biom" --server-cert $QIITA_SERVER_CERT
6868
- source deactivate
6969
- source activate qiita
7070
before_script:

qiita_db/handlers/artifact.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88

99
from tornado.web import HTTPError
1010
from collections import defaultdict
11-
from json import loads
11+
from json import loads, dumps
1212

13+
from qiita_core.qiita_settings import r_client
1314
import qiita_db as qdb
1415
from .oauth2 import OauthBaseHandler, authenticate_oauth
1516

@@ -234,3 +235,68 @@ def post(self):
234235
self.set_status(200, reason="Artifact type already exists")
235236

236237
self.finish()
238+
239+
240+
class APIArtifactHandler(OauthBaseHandler):
241+
@authenticate_oauth
242+
def post(self):
243+
user_email = self.get_argument('user_email')
244+
job_id = self.get_argument('job_id', None)
245+
prep_id = self.get_argument('prep_id', None)
246+
atype = self.get_argument('artifact_type')
247+
aname = self.get_argument('command_artifact_name', 'Name')
248+
files = self.get_argument('files')
249+
250+
if job_id is None and prep_id is None:
251+
raise HTTPError(
252+
400, reason='You need to specify a job_id or a prep_id')
253+
if job_id is not None and prep_id is not None:
254+
raise HTTPError(
255+
400, reason='You need to specify only a job_id or a prep_id')
256+
257+
user = qdb.user.User(user_email)
258+
values = {
259+
'files': files, 'artifact_type': atype, 'name': aname,
260+
# leaving here in case we need to add a way to add an artifact
261+
# directly to an analysis, for more information see
262+
# ProcessingJob._complete_artifact_transformation
263+
'analysis': None}
264+
PJ = qdb.processing_job.ProcessingJob
265+
if job_id is not None:
266+
TN = qdb.sql_connection.TRN
267+
job = PJ(job_id)
268+
with TN:
269+
sql = """SELECT command_output_id
270+
FROM qiita.command_output
271+
WHERE name = %s AND command_id = %s"""
272+
TN.add(sql, [aname, job.command.id])
273+
results = TN.execute_fetchflatten()
274+
if len(results) < 1:
275+
raise HTTPError(400, 'The command_artifact_name does not '
276+
'exist in the command')
277+
cmd_out_id = results[0]
278+
provenance = {'job': job_id,
279+
'cmd_out_id': cmd_out_id,
280+
# direct_creation is a flag to avoid having to wait
281+
# for the complete job to create the new artifact,
282+
# which is normally ran during regular processing.
283+
# Skipping is fine because we are adding an artifact
284+
# to an existing job outside of regular processing
285+
'direct_creation': True,
286+
'name': aname}
287+
values['provenance'] = dumps(provenance)
288+
prep_id = job.input_artifacts[0].id
289+
else:
290+
prep_id = int(prep_id)
291+
292+
values['template'] = prep_id
293+
cmd = qdb.software.Command.get_validator(atype)
294+
params = qdb.software.Parameters.load(cmd, values_dict=values)
295+
new_job = PJ.create(user, params, True)
296+
new_job.submit()
297+
298+
r_client.set('prep_template_%d' % prep_id,
299+
dumps({'job_id': new_job.id, 'is_qiita_job': True}))
300+
301+
self.write(new_job.id)
302+
self.finish()

qiita_db/handlers/tests/test_artifact.py

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
# -----------------------------------------------------------------------------
88

99
from unittest import main, TestCase
10-
from json import loads
10+
from json import loads, dumps
1111
from functools import partial
1212
from os.path import join, exists, isfile
1313
from os import close, remove
1414
from shutil import rmtree
1515
from tempfile import mkstemp, mkdtemp
16-
from json import dumps
16+
from time import sleep
1717

1818
from tornado.web import HTTPError
1919
import pandas as pd
@@ -288,5 +288,100 @@ def test_post(self):
288288
self.assertEqual(obs.code, 200)
289289

290290

291+
class APIArtifactHandlerTests(OauthTestingBase):
292+
def setUp(self):
293+
super(APIArtifactHandlerTests, self).setUp()
294+
self._clean_up_files = []
295+
296+
def tearDown(self):
297+
super(APIArtifactHandlerTests, self).tearDown()
298+
299+
for f in self._clean_up_files:
300+
if exists(f):
301+
remove(f)
302+
303+
def test_post(self):
304+
# no header
305+
obs = self.post('/qiita_db/artifact/', data={})
306+
self.assertEqual(obs.code, 400)
307+
308+
fd, fp = mkstemp(suffix='_table.biom')
309+
close(fd)
310+
# renaming samples
311+
et.update_ids({'S1': '1.SKB1.640202',
312+
'S2': '1.SKD3.640198',
313+
'S3': '1.SKM4.640180'}, inplace=True)
314+
with biom_open(fp, 'w') as f:
315+
et.to_hdf5(f, "test")
316+
self._clean_up_files.append(fp)
317+
318+
# no job_id or prep_id
319+
data = {'user_email': '[email protected]',
320+
'artifact_type': 'BIOM',
321+
'command_artifact_name': 'OTU table',
322+
'files': dumps({'biom': [fp]})}
323+
324+
obs = self.post('/qiita_db/artifact/', headers=self.header, data=data)
325+
self.assertEqual(obs.code, 400)
326+
self.assertIn(
327+
'You need to specify a job_id or a prep_id', str(obs.error))
328+
329+
# both job_id and prep_id defined
330+
data['job_id'] = 'e5609746-a985-41a1-babf-6b3ebe9eb5a9'
331+
data['prep_id'] = 'prep_id'
332+
obs = self.post('/qiita_db/artifact/', headers=self.header, data=data)
333+
self.assertEqual(obs.code, 400)
334+
self.assertIn(
335+
'You need to specify only a job_id or a prep_id', str(obs.error))
336+
337+
# make sure that all the plugins are on
338+
qdb.util.activate_or_update_plugins(update=True)
339+
340+
# tests success by inserting a new artifact into an existing job
341+
original_job = qdb.processing_job.ProcessingJob(data['job_id'])
342+
input_artifact = original_job.input_artifacts[0]
343+
self.assertEqual(len(input_artifact.children), 3)
344+
# send the new data
345+
del data['prep_id']
346+
obs = self.post('/qiita_db/artifact/', headers=self.header, data=data)
347+
jid = obs.body.decode("utf-8")
348+
349+
job = qdb.processing_job.ProcessingJob(jid)
350+
while job.status not in ('error', 'success'):
351+
sleep(0.5)
352+
353+
# now the original job should have 4 children and make sure they have
354+
# the same parent and parameters
355+
children = input_artifact.children
356+
self.assertEqual(len(children), 4)
357+
for c in children[1:]:
358+
self.assertCountEqual(children[0].processing_parameters.values,
359+
c.processing_parameters.values)
360+
self.assertEqual(children[0].parents, c.parents)
361+
362+
# now let's test adding an artifact directly to a new prep
363+
new_prep = qdb.metadata_template.prep_template.PrepTemplate.create(
364+
pd.DataFrame({'new_col': {'1.SKB1.640202': 1,
365+
'1.SKD3.640198': 2,
366+
'1.SKM4.640180': 3}}),
367+
qdb.study.Study(1), '16S')
368+
fd, fp = mkstemp(suffix='_table.biom')
369+
close(fd)
370+
with biom_open(fp, 'w') as f:
371+
et.to_hdf5(f, "test")
372+
self._clean_up_files.append(fp)
373+
data = {'user_email': '[email protected]',
374+
'artifact_type': 'BIOM', 'prep_id': new_prep.id,
375+
'files': dumps({'biom': [fp]})}
376+
377+
obs = self.post('/qiita_db/artifact/', headers=self.header, data=data)
378+
jid = obs.body.decode("utf-8")
379+
380+
job = qdb.processing_job.ProcessingJob(jid)
381+
while job.status not in ('error', 'success'):
382+
sleep(0.5)
383+
self.assertIsNotNone(new_prep.artifact)
384+
385+
291386
if __name__ == '__main__':
292387
main()

qiita_db/processing_job.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,14 @@ def _complete_artifact_definition(self, artifact_data):
11131113
Dict with the artifact information. `filepaths` contains the list
11141114
of filepaths and filepath types for the artifact and
11151115
`artifact_type` the type of the artifact
1116+
1117+
Notes
1118+
-----
1119+
The `provenance` in the job.parameters can contain a `direct_creation`
1120+
flag to avoid having to wait for the complete job to create a new
1121+
artifact, which is normally ran during regular processing. Skipping is
1122+
fine because we are adding an artifact to an existing job outside of
1123+
regular processing
11161124
"""
11171125
with qdb.sql_connection.TRN:
11181126
atype = artifact_data['artifact_type']
@@ -1123,18 +1131,30 @@ def _complete_artifact_definition(self, artifact_data):
11231131
if job_params['provenance'] is not None:
11241132
# The artifact is a result from a previous job
11251133
provenance = loads(job_params['provenance'])
1126-
if provenance.get('data_type') is not None:
1127-
artifact_data = {'data_type': provenance['data_type'],
1128-
'artifact_data': artifact_data}
1129-
1130-
sql = """UPDATE qiita.processing_job_validator
1131-
SET artifact_info = %s
1132-
WHERE validator_id = %s"""
1133-
qdb.sql_connection.TRN.add(
1134-
sql, [dumps(artifact_data), self.id])
1135-
qdb.sql_connection.TRN.execute()
1136-
# Can't create the artifact until all validators are completed
1137-
self._set_status('waiting')
1134+
if provenance.get('direct_creation', False):
1135+
original_job = ProcessingJob(provenance['job'])
1136+
qdb.artifact.Artifact.create(
1137+
filepaths, atype,
1138+
parents=original_job.input_artifacts,
1139+
processing_parameters=original_job.parameters,
1140+
analysis=job_params['analysis'],
1141+
name=job_params['name'])
1142+
self._set_status('success')
1143+
else:
1144+
if provenance.get('data_type') is not None:
1145+
artifact_data = {'data_type': provenance['data_type'],
1146+
'artifact_data': artifact_data}
1147+
1148+
sql = """UPDATE qiita.processing_job_validator
1149+
SET artifact_info = %s
1150+
WHERE validator_id = %s"""
1151+
qdb.sql_connection.TRN.add(
1152+
sql, [dumps(artifact_data), self.id])
1153+
qdb.sql_connection.TRN.execute()
1154+
1155+
# Can't create the artifact until all validators
1156+
# are completed
1157+
self._set_status('waiting')
11381158
else:
11391159
# The artifact is uploaded by the user or is the initial
11401160
# artifact of an analysis

qiita_pet/webserver.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@
6060
JobHandler, HeartbeatHandler, ActiveStepHandler, CompleteHandler,
6161
ProcessingJobAPItestHandler)
6262
from qiita_db.handlers.artifact import (
63-
ArtifactHandler, ArtifactAPItestHandler, ArtifactTypeHandler)
63+
ArtifactHandler, ArtifactAPItestHandler, ArtifactTypeHandler,
64+
APIArtifactHandler)
6465
from qiita_db.handlers.sample_information import SampleInfoDBHandler
6566
from qiita_db.handlers.user import UserInfoDBHandler, UsersListDBHandler
6667
from qiita_db.handlers.prep_template import (
@@ -205,6 +206,7 @@ def __init__(self):
205206
(r"/qiita_db/jobs/(.*)", JobHandler),
206207
(r"/qiita_db/artifacts/types/", ArtifactTypeHandler),
207208
(r"/qiita_db/artifacts/(.*)/", ArtifactHandler),
209+
(r"/qiita_db/artifact/", APIArtifactHandler),
208210
(r"/qiita_db/users/", UsersListDBHandler),
209211
(r"/qiita_db/user/(.*)/data/", UserInfoDBHandler),
210212
(r"/qiita_db/sample_information/(.*)/data/", SampleInfoDBHandler),

0 commit comments

Comments
 (0)