def submit_answered_survey(account_id, source_id, language_tag, body, token_info): _validate_account_access(token_info, account_id) if body['survey_template_id'] == SurveyTemplateRepo.VIOSCREEN_ID: return _submit_vioscreen_status(account_id, source_id, body["survey_text"]["key"]) # TODO: Is this supposed to return new survey id? # TODO: Rename survey_text to survey_model/model to match Vue's naming? with Transaction() as t: survey_answers_repo = SurveyAnswersRepo(t) survey_answers_id = survey_answers_repo.submit_answered_survey( account_id, source_id, language_tag, body["survey_template_id"], body["survey_text"]) t.commit() response = flask.Response() response.status_code = 201 response.headers['Location'] = '/api/accounts/%s' \ '/sources/%s' \ '/surveys/%s' % \ (account_id, source_id, survey_answers_id) return response
def create_human_source_from_consent(account_id, body, token_info): _validate_account_access(token_info, account_id) # Must convert consent form body into object processable by create_source. # Not adding any error handling here because if 'participant_name' isn't # here, we SHOULD be getting an error. source = { 'source_type': Source.SOURCE_TYPE_HUMAN, 'source_name': body['participant_name'], 'consent': { 'participant_email': body['participant_email'], 'age_range': body['age_range'] } } deceased_parent_key = 'deceased_parent' child_keys = { 'parent_1_name', 'parent_2_name', deceased_parent_key, 'obtainer_name' } intersection = child_keys.intersection(body) if intersection: source['consent']['child_info'] = {} for key in intersection: if key == deceased_parent_key: body[deceased_parent_key] = body[deceased_parent_key] == 'true' source['consent']['child_info'][key] = body[key] # NB: Don't expect to handle errors 404, 422 in this function; expect to # farm out to `create_source` return create_source(account_id, source, token_info)
def top_food_report(account_id, source_id, survey_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: vioscreen_repo = VioscreenRepo(t) # Vioscreen username is our survey_id status = vioscreen_repo.get_vioscreen_status(account_id, source_id, survey_id) if status != 3: # Oops, we don't have results available for this one raise NotFound("No such survey recorded") vio = VioscreenAdminAPI() sessions = vio.sessions(survey_id) # Looks like vioscreen supports multiple sessions per user, do we care? session_id = sessions[0]['sessionId'] report = vio.top_food_report(session_id) response = make_response(report) response.headers.set("Content-Type", "application/pdf") # TODO: Do we want it to download a file or be embedded in the html? # response.headers.set('Content-Disposition', # 'attachment', # filename='top-food-report.pdf') return response
def read_survey_templates(account_id, source_id, language_tag, token_info): _validate_account_access(token_info, account_id) # TODO: I don't think surveys have names... only survey groups have names. # So what can I pass down to the user that will make any sense here? # Note to future maintainers, # 2/21/20: currently the only way to figure this out # is to look through the "surveys" and "survey_group" tables, try: # select survey_id, american from surveys left join survey_group on # survey_group = group_order; with Transaction() as t: source_repo = SourceRepo(t) source = source_repo.get_source(account_id, source_id) if source is None: return jsonify(code=404, message="No source found"), 404 template_repo = SurveyTemplateRepo(t) if source.source_type == Source.SOURCE_TYPE_HUMAN: return jsonify([ template_repo.get_survey_template_link_info(x) for x in [1, 3, 4, 5, 6, SurveyTemplateRepo.VIOSCREEN_ID] ]), 200 elif source.source_type == Source.SOURCE_TYPE_ANIMAL: return jsonify([ template_repo.get_survey_template_link_info(x) for x in [2] ]), 200 else: return jsonify([]), 200
def update_sample_association(account_id, source_id, sample_id, body, token_info): _validate_account_access(token_info, account_id) # TODO: API layer doesn't understand that BadRequest can be thrown, # but that looks to be the right result if sample_site bad. # Need to update the api layer if we want to specify 400s. # (Or we leave api as is and say 400's can always be thrown if your # request is bad) with Transaction() as t: sample_repo = SampleRepo(t) source_repo = SourceRepo(t) source = source_repo.get_source(account_id, source_id) if source is None: return jsonify(code=404, message="No such source"), 404 needs_sample_site = source.source_type in [ Source.SOURCE_TYPE_HUMAN, Source.SOURCE_TYPE_ANIMAL ] precludes_sample_site = source.source_type == \ Source.SOURCE_TYPE_ENVIRONMENT sample_site_present = "sample_site" in body and \ body["sample_site"] is not None if needs_sample_site and not sample_site_present: # Human/Animal sources require sample_site to be set raise BadRequest("human/animal samples require sample_site") if precludes_sample_site and sample_site_present: raise BadRequest("environmental samples cannot specify " "sample_site") sample_datetime = body['sample_datetime'] try: sample_datetime = fromisotime(sample_datetime) except ValueError: raise BadRequest("Invalid sample_datetime") curdate = datetime.now(sample_datetime.tzinfo) lower_limit = curdate + relativedelta(years=-10) upper_limit = curdate + relativedelta(months=+1) if sample_datetime < lower_limit or sample_datetime > upper_limit: raise BadRequest('Invalid sample date') # sample_site will not be present if its environmental. this will # default to None if the key is not present sample_site = body.get('sample_site') sample_info = SampleInfo(sample_id, sample_datetime, sample_site, body["sample_notes"]) is_admin = token_grants_admin_access(token_info) sample_repo.update_info(account_id, source_id, sample_info, override_locked=is_admin) final_sample = sample_repo.get_sample(account_id, source_id, sample_id) t.commit() return jsonify(final_sample), 200
def read_sample_associations(account_id, source_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) samples = sample_repo.get_samples_by_source(account_id, source_id) api_samples = [x.to_api() for x in samples] return jsonify(api_samples), 200
def read_source(account_id, source_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: source_repo = SourceRepo(t) source = source_repo.get_source(account_id, source_id) if source is None: return jsonify(code=404, message=SRC_NOT_FOUND_MSG), 404 return jsonify(source.to_api()), 200
def read_sources(account_id, token_info, source_type=None): _validate_account_access(token_info, account_id) with Transaction() as t: source_repo = SourceRepo(t) sources = source_repo.get_sources_in_account(account_id, source_type) api_sources = [x.to_api() for x in sources] # TODO: Also support 404? Or is that not necessary? return jsonify(api_sources), 200
def read_sample_association(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) sample = sample_repo.get_sample(account_id, source_id, sample_id) if sample is None: return jsonify(code=404, message="Sample not found"), 404 return jsonify(sample.to_api()), 200
def delete_source(account_id, source_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: source_repo = SourceRepo(t) if not source_repo.delete_source(account_id, source_id): return jsonify(code=404, message=SRC_NOT_FOUND_MSG), 404 # TODO: 422? t.commit() return '', 204
def dissociate_answered_survey(account_id, source_id, sample_id, survey_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: answers_repo = SurveyAnswersRepo(t) answers_repo.dissociate_answered_survey_from_sample( account_id, source_id, sample_id, survey_id) t.commit() return '', 204
def read_sample_vioscreen_session(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) is_error, vioscreen_session = _get_session_by_account_details( account_id, source_id, sample_id) if is_error: return vioscreen_session return jsonify(vioscreen_session[0].to_api()), 200
def read_survey_template(account_id, source_id, survey_template_id, language_tag, token_info, survey_redirect_url=None): _validate_account_access(token_info, account_id) # TODO: can we get rid of source_id? I don't have anything useful to do # with it... I guess I could check if the source is a dog before giving # out a pet information survey? with Transaction() as t: survey_template_repo = SurveyTemplateRepo(t) info = survey_template_repo.get_survey_template_link_info( survey_template_id) # For external surveys, we generate links pointing out if survey_template_id == SurveyTemplateRepo.VIOSCREEN_ID: url = vioscreen.gen_survey_url( language_tag, survey_redirect_url ) # TODO FIXME HACK: This field's contents are not specified! info.survey_template_text = { "url": url } return jsonify(info), 200 # For local surveys, we generate the json representing the survey survey_template = survey_template_repo.get_survey_template( survey_template_id, language_tag) info.survey_template_text = vue_adapter.to_vue_schema(survey_template) # TODO FIXME HACK: We need a better way to enforce validation on fields # that need it, can this be stored adjacent to the survey questions? client_side_validation = { "108": { # Height "inputType": "number", "validator": "number", "min": 0, "max": None }, "113": { # Weight "inputType": "number", "validator": "number", "min": 0, "max": None } } for group in info.survey_template_text.groups: for field in group.fields: if field.id in client_side_validation: field.set(**client_side_validation[field.id]) return jsonify(info), 200
def render_consent_doc(account_id, language_tag, token_info): _validate_account_access(token_info, account_id) # NB: Do NOT need to explicitly pass account_id into template for # integration into form submission URL because form submit URL builds on # the base of the URL that called it (which includes account_id) localization_info = localization.LANG_SUPPORT[language_tag] content = localization_info[localization.NEW_PARTICIPANT_KEY] return jsonify(content), 200
def associate_sample(account_id, source_id, body, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) sample_repo.associate_sample(account_id, source_id, body['sample_id']) t.commit() response = flask.Response() response.status_code = 201 response.headers['Location'] = '/api/accounts/%s/sources/%s/samples/%s' % \ (account_id, source_id, body['sample_id']) return response
def read_answered_surveys(account_id, source_id, language_tag, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: survey_answers_repo = SurveyAnswersRepo(t) survey_template_repo = SurveyTemplateRepo(t) answered_surveys = survey_answers_repo.list_answered_surveys( account_id, source_id) api_objs = [] for ans in answered_surveys: template_id = survey_answers_repo.find_survey_template_id(ans) if template_id is None: continue o = survey_template_repo.get_survey_template_link_info(template_id) api_objs.append(o.to_api(ans)) return jsonify(api_objs), 200
def read_sample_vioscreen_mpeds(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) is_error, vioscreen_session = _get_session_by_account_details( account_id, source_id, sample_id) if is_error: return vioscreen_session with Transaction() as t: vio_mped = VioscreenMPedsRepo(t) vioscreen_mpeds = vio_mped.get_mpeds(vioscreen_session[0].sessionId) if vioscreen_mpeds is None: return jsonify(code=404, message="MPeds not found"), 404 return jsonify(vioscreen_mpeds.to_api()), 200
def delete_source(account_id, source_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: source_repo = SourceRepo(t) survey_answers_repo = SurveyAnswersRepo(t) answers = survey_answers_repo.list_answered_surveys( account_id, source_id) for survey_id in answers: survey_answers_repo.delete_answered_survey(account_id, survey_id) if not source_repo.delete_source(account_id, source_id): return jsonify(code=404, message=SRC_NOT_FOUND_MSG), 404 # TODO: 422? t.commit() return '', 204
def read_sample_vioscreen_food_consumption(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) is_error, vioscreen_session = _get_session_by_account_details( account_id, source_id, sample_id) if is_error: return vioscreen_session with Transaction() as t: vio_cons = VioscreenFoodConsumptionRepo(t) vioscreen_food_consumption = vio_cons.get_food_consumption( vioscreen_session[0].sessionId) if vioscreen_food_consumption is None: return jsonify(code=404, message="Food Consumption not found"), 404 return jsonify(vioscreen_food_consumption.to_api()), 200
def dissociate_sample(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: answers_repo = SurveyAnswersRepo(t) answered_survey_ids = answers_repo.list_answered_surveys_by_sample( account_id, source_id, sample_id) for curr_answered_survey_id in answered_survey_ids: answers_repo.dissociate_answered_survey_from_sample( account_id, source_id, sample_id, curr_answered_survey_id) sample_repo = SampleRepo(t) sample_repo.dissociate_sample(account_id, source_id, sample_id) t.commit() return '', 204
def read_sample_vioscreen_dietary_score(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) is_error, vioscreen_session = _get_session_by_account_details( account_id, source_id, sample_id) if is_error: return vioscreen_session with Transaction() as t: vio_diet = VioscreenDietaryScoreRepo(t) vioscreen_dietary_scores = vio_diet.get_dietary_scores( vioscreen_session[0].sessionId) if vioscreen_dietary_scores is None: return jsonify(code=404, message="Dietary Score not found"), 404 return jsonify([vds.to_api() for vds in vioscreen_dietary_scores]), 200
def read_sample_vioscreen_percent_energy(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) is_error, vioscreen_session = _get_session_by_account_details( account_id, source_id, sample_id) if is_error: return vioscreen_session with Transaction() as t: vio_perc = VioscreenPercentEnergyRepo(t) vioscreen_percent_energy = vio_perc.get_percent_energy( vioscreen_session[0].sessionId) if vioscreen_percent_energy is None: return jsonify(code=404, message="Percent Energy not found"), 404 return jsonify(vioscreen_percent_energy.to_api()), 200
def associate_answered_survey(account_id, source_id, sample_id, body, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: answers_repo = SurveyAnswersRepo(t) answers_repo.associate_answered_survey_with_sample( account_id, source_id, sample_id, body['survey_id']) t.commit() response = flask.Response() response.status_code = 201 response.headers['Location'] = '/api/accounts/%s' \ '/sources/%s' \ '/surveys/%s' % \ (account_id, source_id, body['survey_id']) return response
def read_answered_survey_associations(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: answers_repo = SurveyAnswersRepo(t) template_repo = SurveyTemplateRepo(t) answered_surveys = answers_repo.list_answered_surveys_by_sample( account_id, source_id, sample_id) resp_obj = [] for answered_survey in answered_surveys: template_id = answers_repo.find_survey_template_id(answered_survey) if template_id is None: continue info = template_repo.get_survey_template_link_info(template_id) resp_obj.append(info.to_api(answered_survey)) t.commit() return jsonify(resp_obj), 200
def read_answered_survey(account_id, source_id, survey_id, language_tag, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: survey_answers_repo = SurveyAnswersRepo(t) survey_answers = survey_answers_repo.get_answered_survey( account_id, source_id, survey_id, language_tag) if not survey_answers: return jsonify(code=404, message="No survey answers found"), 404 template_id = survey_answers_repo.find_survey_template_id(survey_id) if template_id is None: return jsonify(code=422, message="No answers in survey"), 422 template_repo = SurveyTemplateRepo(t) link_info = template_repo.get_survey_template_link_info(template_id) link_info.survey_id = survey_id link_info.survey_text = survey_answers return jsonify(link_info), 200
def create_source(account_id, body, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: source_repo = SourceRepo(t) source_id = str(uuid.uuid4()) name = body["source_name"] source_type = body['source_type'] if source_type == Source.SOURCE_TYPE_HUMAN: # TODO: Unfortunately, humans require a lot of special handling, # and we started mixing Source calls used for transforming to/ # from the database with source calls to/from the api. # Would be nice to split this out better. source_info = HumanInfo.from_dict(body, consent_date=date.today(), date_revoked=None) # the "legacy" value of the age_range enum is not valid to use when # creating a new source, so do not allow that. # NB: Not necessary to do this check when updating a source as # only source name and description (not age_range) may be updated. if source_info.age_range == "legacy": raise RepoException("Age range may not be set to legacy.") else: source_info = NonHumanInfo.from_dict(body) new_source = Source(source_id, account_id, source_type, name, source_info) source_repo.create_source(new_source) # Must pull from db to get creation_time, update_time s = source_repo.get_source(account_id, new_source.id) t.commit() response = jsonify(s.to_api()) response.status_code = 201 response.headers['Location'] = '/api/accounts/%s/sources/%s' % \ (account_id, source_id) return response
def read_sample_association(account_id, source_id, sample_id, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: sample_repo = SampleRepo(t) sample = sample_repo.get_sample(account_id, source_id, sample_id) if sample is None: return jsonify(code=404, message="Sample not found"), 404 qiita_body = {'sample_ids': ["10317." + str(sample.barcode)]} try: qiita_data = qclient.post('/api/v1/study/10317/samples/status', json=qiita_body) accession_urls = [] for barcode_info in qiita_data: experiment_accession = barcode_info.get("ebi_experiment_accession") if experiment_accession is None: continue accession_urls.append("https://www.ebi.ac.uk/ena/browser/view/" + experiment_accession + "?show=reads") sample.set_accession_urls(accession_urls) except NotFoundError: # I guess qiita doesn't know about this barcode, # so probably no ebi accession info pass except BadRequestError: # How do I log these to gunicorn?? app.logger.warning("Couldn't communicate with qiita", exc_info=True) except ForbiddenError: # How do I log these to gunicorn?? app.logger.warning("Couldn't communicate with qiita", exc_info=True) except RuntimeError: # How do I log these to gunicorn?? app.logger.warning("Couldn't communicate with qiita", exc_info=True) raise return jsonify(sample.to_api()),
def update_source(account_id, source_id, body, token_info): _validate_account_access(token_info, account_id) with Transaction() as t: source_repo = SourceRepo(t) source = source_repo.get_source(account_id, source_id) if source is None: return jsonify(code=404, message=SRC_NOT_FOUND_MSG), 404 source.name = body["source_name"] # every type of source has a name but not every type has a description if getattr(source.source_data, "description", False): source.source_data.description = body.get("source_description", None) source_repo.update_source_data_api_fields(source) # I wonder if there's some way to get the creation_time/update_time # during the insert/update... source = source_repo.get_source(account_id, source_id) t.commit() # TODO: 422? Not sure this can actually happen anymore ... return jsonify(source.to_api()), 200
def read_survey_template(account_id, source_id, survey_template_id, language_tag, token_info, survey_redirect_url=None, vioscreen_ext_sample_id=None): _validate_account_access(token_info, account_id) # TODO: can we get rid of source_id? I don't have anything useful to do # with it... I guess I could check if the source is a dog before giving # out a pet information survey? with Transaction() as t: survey_template_repo = SurveyTemplateRepo(t) info = survey_template_repo.get_survey_template_link_info( survey_template_id) # For external surveys, we generate links pointing out if survey_template_id == SurveyTemplateRepo.VIOSCREEN_ID: if vioscreen_ext_sample_id: # User is about to start a vioscreen survey for this sample # record this in the database. db_vioscreen_id = survey_template_repo.create_vioscreen_id( account_id, source_id, vioscreen_ext_sample_id) else: raise ValueError("Vioscreen Template requires " "vioscreen_ext_sample_id parameter.") (birth_year, gender ) = survey_template_repo.fetch_user_birth_year_gender(account_id) url = vioscreen.gen_survey_url(db_vioscreen_id, language_tag, survey_redirect_url, birth_year=birth_year, gender=gender) # TODO FIXME HACK: This field's contents are not specified! info.survey_template_text = {"url": url} t.commit() return jsonify(info), 200 # For local surveys, we generate the json representing the survey survey_template = survey_template_repo.get_survey_template( survey_template_id, language_tag) info.survey_template_text = vue_adapter.to_vue_schema(survey_template) # TODO FIXME HACK: We need a better way to enforce validation on fields # that need it, can this be stored adjacent to the survey questions? client_side_validation = { "108": { # Height "inputType": "number", "validator": "number", "min": 0, "max": None }, "113": { # Weight "inputType": "number", "validator": "number", "min": 0, "max": None } } for group in info.survey_template_text.groups: for field in group.fields: if field.id in client_side_validation: field.set(**client_side_validation[field.id]) return jsonify(info), 200
def read_survey_template(account_id, source_id, survey_template_id, language_tag, token_info, survey_redirect_url=None, vioscreen_ext_sample_id=None): _validate_account_access(token_info, account_id) with Transaction() as t: survey_template_repo = SurveyTemplateRepo(t) info = survey_template_repo.get_survey_template_link_info( survey_template_id) remote_surveys = set(survey_template_repo.remote_surveys()) # For external surveys, we generate links pointing out if survey_template_id in remote_surveys: if survey_template_id == SurveyTemplateRepo.VIOSCREEN_ID: url = _remote_survey_url_vioscreen(t, account_id, source_id, language_tag, survey_redirect_url, vioscreen_ext_sample_id) elif survey_template_id == SurveyTemplateRepo.MYFOODREPO_ID: url = _remote_survey_url_myfoodrepo(t, account_id, source_id, language_tag) elif survey_template_id == SurveyTemplateRepo.POLYPHENOL_FFQ_ID: url = _remote_survey_url_polyphenol_ffq( t, account_id, source_id, language_tag) else: raise ValueError(f"Cannot generate URL for survey " f"{survey_template_id}") # TODO FIXME HACK: This field's contents are not specified! info.survey_template_text = {"url": url} t.commit() return jsonify(info), 200 # For local surveys, we generate the json representing the survey survey_template = survey_template_repo.get_survey_template( survey_template_id, language_tag) info.survey_template_text = vue_adapter.to_vue_schema(survey_template) # TODO FIXME HACK: We need a better way to enforce validation on fields # that need it, can this be stored adjacent to the survey questions? client_side_validation = { "108": { # Height "inputType": "number", "validator": "integer", "min": 0, "max": None }, "113": { # Weight "inputType": "number", "validator": "integer", "min": 0, "max": None } } for group in info.survey_template_text.groups: for field in group.fields: if field.id in client_side_validation: field.set(**client_side_validation[field.id]) return jsonify(info), 200