-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A minimal REST API for Qiita #2094
Changes from all commits
b806436
dabad2f
a9f4eeb
5e73446
ab8a067
64862f2
748a49f
346fd2f
dc0f7c6
553f816
6df3420
9981b06
99a8eb8
3888571
a77736c
69c63c3
6b2e3f6
cee5dce
e19e6fa
6ad2ef2
a0f7463
9218611
d3fb608
cfe09d6
9c3d42a
ca34017
fd54712
43e133e
a18b5d8
b32d906
13eb32c
0872833
09f06bd
d4e010c
53b3803
1a3808c
23c9993
e460390
e76e82a
3f58dcd
f243a1d
595d9a1
e337632
9f528ea
e970392
133b9f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# ----------------------------------------------------------------------------- | ||
# 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 .study import StudyHandler, StudyCreatorHandler, StudyStatusHandler | ||
from .study_samples import (StudySamplesHandler, StudySamplesInfoHandler, | ||
StudySamplesCategoriesHandler) | ||
from .study_person import StudyPersonHandler | ||
from .study_preparation import (StudyPrepCreatorHandler, | ||
StudyPrepArtifactCreatorHandler) | ||
|
||
|
||
__all__ = ['StudyHandler', 'StudySamplesHandler', 'StudySamplesInfoHandler', | ||
'StudySamplesCategoriesHandler', 'StudyPersonHandler', | ||
'StudyCreatorHandler', 'StudyPrepCreatorHandler', | ||
'StudyPrepArtifactCreatorHandler', 'StudyStatusHandler'] | ||
|
||
|
||
ENDPOINTS = ( | ||
(r"/api/v1/study$", StudyCreatorHandler), | ||
(r"/api/v1/study/([0-9]+)$", StudyHandler), | ||
(r"/api/v1/study/([0-9]+)/samples/categories=([a-zA-Z\-0-9\.:,_]*)", | ||
StudySamplesCategoriesHandler), | ||
(r"/api/v1/study/([0-9]+)/samples", StudySamplesHandler), | ||
(r"/api/v1/study/([0-9]+)/samples/info", StudySamplesInfoHandler), | ||
(r"/api/v1/person(.*)", StudyPersonHandler), | ||
(r"/api/v1/study/([0-9]+)/preparation/([0-9]+)/artifact", | ||
StudyPrepArtifactCreatorHandler), | ||
(r"/api/v1/study/([0-9]+)/preparation(.*)", StudyPrepCreatorHandler), | ||
(r"/api/v1/study/([0-9]+)/status$", StudyStatusHandler) | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# ----------------------------------------------------------------------------- | ||
# 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 qiita_db.study import Study | ||
from qiita_db.exceptions import QiitaDBUnknownIDError | ||
from qiita_pet.handlers.util import to_int | ||
from qiita_pet.handlers.base_handlers import BaseHandler | ||
|
||
|
||
class RESTHandler(BaseHandler): | ||
def fail(self, msg, status, **kwargs): | ||
out = {'message': msg} | ||
out.update(kwargs) | ||
|
||
self.write(out) | ||
self.set_status(status) | ||
self.finish() | ||
|
||
def safe_get_study(self, study_id): | ||
study_id = to_int(study_id) | ||
s = None | ||
try: | ||
s = Study(study_id) | ||
except QiitaDBUnknownIDError: | ||
self.fail('Study not found', 404) | ||
finally: | ||
return s |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
# ----------------------------------------------------------------------------- | ||
# 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. | ||
# ----------------------------------------------------------------------------- | ||
import warnings | ||
|
||
from tornado.escape import json_decode | ||
|
||
from qiita_db.handlers.oauth2 import authenticate_oauth | ||
from qiita_db.study import StudyPerson, Study | ||
from qiita_db.user import User | ||
from .rest_handler import RESTHandler | ||
from qiita_db.metadata_template.constants import SAMPLE_TEMPLATE_COLUMNS | ||
|
||
|
||
class StudyHandler(RESTHandler): | ||
|
||
@authenticate_oauth | ||
def get(self, study_id): | ||
study = self.safe_get_study(study_id) | ||
if study is None: | ||
return | ||
|
||
info = study.info | ||
pi = info['principal_investigator'] | ||
lp = info['lab_person'] | ||
self.write({'title': study.title, | ||
'contacts': {'principal_investigator': [ | ||
pi.name, | ||
pi.affiliation, | ||
pi.email], | ||
'lab_person': [ | ||
lp.name, | ||
lp.affiliation, | ||
lp.email]}, | ||
'study_abstract': info['study_abstract'], | ||
'study_description': info['study_description'], | ||
'study_alias': info['study_alias']}) | ||
self.finish() | ||
|
||
|
||
class StudyCreatorHandler(RESTHandler): | ||
|
||
@authenticate_oauth | ||
def post(self): | ||
try: | ||
payload = json_decode(self.request.body) | ||
except ValueError: | ||
self.fail('Could not parse body', 400) | ||
return | ||
|
||
required = {'title', 'study_abstract', 'study_description', | ||
'study_alias', 'owner', 'contacts'} | ||
|
||
if not required.issubset(payload): | ||
self.fail('Not all required arguments provided', 400) | ||
return | ||
|
||
title = payload['title'] | ||
study_abstract = payload['study_abstract'] | ||
study_desc = payload['study_description'] | ||
study_alias = payload['study_alias'] | ||
|
||
owner = payload['owner'] | ||
if not User.exists(owner): | ||
self.fail('Unknown user', 403) | ||
return | ||
else: | ||
owner = User(owner) | ||
|
||
contacts = payload['contacts'] | ||
|
||
if Study.exists(title): | ||
self.fail('Study title already exists', 409) | ||
return | ||
|
||
pi_name = contacts['principal_investigator'][0] | ||
pi_aff = contacts['principal_investigator'][1] | ||
if not StudyPerson.exists(pi_name, pi_aff): | ||
self.fail('Unknown principal investigator', 403) | ||
return | ||
else: | ||
pi = StudyPerson.from_name_and_affiliation(pi_name, pi_aff) | ||
|
||
lp_name = contacts['lab_person'][0] | ||
lp_aff = contacts['lab_person'][1] | ||
if not StudyPerson.exists(lp_name, lp_aff): | ||
self.fail('Unknown lab person', 403) | ||
return | ||
else: | ||
lp = StudyPerson.from_name_and_affiliation(lp_name, lp_aff) | ||
|
||
info = {'lab_person_id': lp, | ||
'principal_investigator_id': pi, | ||
'study_abstract': study_abstract, | ||
'study_description': study_desc, | ||
'study_alias': study_alias, | ||
|
||
# TODO: we believe it is accurate that mixs is false and | ||
# metadata completion is false as these cannot be known | ||
# at study creation here no matter what. | ||
# we do not know what should be done with the timeseries. | ||
'mixs_compliant': False, | ||
'metadata_complete': False, | ||
'timeseries_type_id': 1} | ||
study = Study.create(owner, title, [1], info) | ||
|
||
self.set_status(201) | ||
self.write({'id': study.id}) | ||
self.finish() | ||
|
||
|
||
class StudyStatusHandler(RESTHandler): | ||
@authenticate_oauth | ||
def get(self, study_id): | ||
study = self.safe_get_study(study_id) | ||
if study is None: | ||
return | ||
|
||
public = study.status == 'public' | ||
st = study.sample_template | ||
sample_information = st is not None | ||
if sample_information: | ||
with warnings.catch_warnings(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these warnings are stored in redis at the template generation time if it is done through the GUI. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awesome, thanks for creating that issue, I just added a link to it. |
||
try: | ||
st.validate(SAMPLE_TEMPLATE_COLUMNS) | ||
except Warning: | ||
sample_information_warnings = True | ||
else: | ||
sample_information_warnings = False | ||
else: | ||
sample_information_warnings = False | ||
|
||
preparations = [] | ||
for prep in study.prep_templates(): | ||
pid = prep.id | ||
art = prep.artifact is not None | ||
# TODO: unclear how to test for warnings on the preparations as | ||
# it requires knowledge of the preparation type. It is possible | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what this refers to? data_type, information_type, other? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it! Depending on the data_type is how the validation happen. One simply option is to return all the validations avaialble? Not pretty but this will allow us to test and figure out a better solution. Other option will be to force the user to pass that, I guess a similar issue than artifact_type/id. BTW I think this is important cause the first use case will be either target gene or shotgun sequencing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'd need to hardcode the variable names of all the prep template constants which can be passed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be englobed here: #2096 |
||
# to tease this out, but it replicates code present in | ||
# PrepTemplate.create, see: | ||
# https://github.com/biocore/qiita/issues/2096 | ||
preparations.append({'id': pid, 'has_artifact': art}) | ||
|
||
self.write({'is_public': public, | ||
'has_sample_information': sample_information, | ||
'sample_information_has_warnings': | ||
sample_information_warnings, | ||
'preparations': preparations}) | ||
self.set_status(200) | ||
self.finish() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# ----------------------------------------------------------------------------- | ||
# 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 qiita_db.handlers.oauth2 import authenticate_oauth | ||
from qiita_db.study import StudyPerson | ||
from qiita_db.exceptions import QiitaDBLookupError | ||
from .rest_handler import RESTHandler | ||
|
||
|
||
class StudyPersonHandler(RESTHandler): | ||
@authenticate_oauth | ||
def get(self, *args, **kwargs): | ||
name = self.get_argument('name') | ||
affiliation = self.get_argument('affiliation') | ||
|
||
try: | ||
p = StudyPerson.from_name_and_affiliation(name, affiliation) | ||
except QiitaDBLookupError: | ||
self.fail('Person not found', 404) | ||
return | ||
|
||
self.write({'address': p.address, 'phone': p.phone, 'email': p.email, | ||
'id': p.id}) | ||
self.finish() | ||
|
||
@authenticate_oauth | ||
def post(self, *args, **kwargs): | ||
name = self.get_argument('name') | ||
affiliation = self.get_argument('affiliation') | ||
email = self.get_argument('email') | ||
|
||
phone = self.get_argument('phone', None) | ||
address = self.get_argument('address', None) | ||
|
||
if StudyPerson.exists(name, affiliation): | ||
self.fail('Person already exists', 409) | ||
return | ||
|
||
p = StudyPerson.create(name=name, affiliation=affiliation, email=email, | ||
phone=phone, address=address) | ||
|
||
self.set_status(201) | ||
self.write({'id': p.id}) | ||
self.finish() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will not require
efo
I will just hardcode it to 1 as it is done everywhere else.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I've just removed this.