aboutsummaryrefslogtreecommitdiff
path: root/gn2/wqflask/views.py
diff options
context:
space:
mode:
authorArun Isaac2023-12-29 18:55:37 +0000
committerArun Isaac2023-12-29 19:01:46 +0000
commit204a308be0f741726b9a620d88fbc22b22124c81 (patch)
treeb3cf66906674020b530c844c2bb4982c8a0e2d39 /gn2/wqflask/views.py
parent83062c75442160427b50420161bfcae2c5c34c84 (diff)
downloadgenenetwork2-204a308be0f741726b9a620d88fbc22b22124c81.tar.gz
Namespace all modules under gn2.
We move all modules under a gn2 directory. This is important for "correct" packaging and deployment as a Guix service.
Diffstat (limited to 'gn2/wqflask/views.py')
-rw-r--r--gn2/wqflask/views.py1328
1 files changed, 1328 insertions, 0 deletions
diff --git a/gn2/wqflask/views.py b/gn2/wqflask/views.py
new file mode 100644
index 00000000..4f636ebc
--- /dev/null
+++ b/gn2/wqflask/views.py
@@ -0,0 +1,1328 @@
+"""Main routing table for GN2"""
+import array
+import base64
+import csv
+import datetime
+import flask
+import hashlib
+import io # Todo: Use cStringIO?
+
+import json
+import numpy as np
+import os
+import pickle as pickle
+import random
+import requests
+import sys
+import traceback
+import uuid
+import xlsxwriter
+
+from functools import reduce
+
+from zipfile import ZipFile
+from zipfile import ZIP_DEFLATED
+
+from uuid import UUID
+
+from urllib.parse import urljoin
+
+from gn2.wqflask import app
+
+from gn3.computations.gemma import generate_hash_of_string
+from flask import current_app
+from flask import g
+from flask import Response
+from flask import request
+from flask import make_response
+from flask import render_template
+from flask import send_from_directory
+from flask import redirect
+from flask import send_file
+from flask import url_for
+from flask import flash
+from flask import session
+
+# Some of these (like collect) might contain endpoints, so they're still used.
+# Blueprints should probably be used instead.
+from gn2.wqflask import collect
+from gn2.wqflask import search_results
+from gn2.wqflask import server_side
+from gn2.base.data_set import create_dataset # Used by YAML in marker_regression
+from gn2.wqflask.show_trait import show_trait
+from gn2.wqflask.show_trait import export_trait_data
+from gn2.wqflask.show_trait.show_trait import get_diff_of_vals
+from gn2.wqflask.heatmap import heatmap
+from gn2.wqflask.external_tools import send_to_bnw
+from gn2.wqflask.external_tools import send_to_webgestalt
+from gn2.wqflask.external_tools import send_to_geneweaver
+from gn2.wqflask.comparison_bar_chart import comparison_bar_chart
+from gn2.wqflask.marker_regression import run_mapping
+from gn2.wqflask.marker_regression.exceptions import NoMappingResultsError
+from gn2.wqflask.marker_regression import display_mapping_results
+from gn2.wqflask.network_graph import network_graph
+from gn2.wqflask.correlation.show_corr_results import set_template_vars
+from gn2.wqflask.correlation.correlation_gn3_api import compute_correlation
+from gn2.wqflask.correlation.rust_correlation import compute_correlation_rust
+from gn2.wqflask.correlation_matrix import show_corr_matrix
+from gn2.wqflask.correlation import corr_scatter_plot
+from gn2.wqflask.correlation.exceptions import WrongCorrelationType
+from gn2.wqflask.ctl.gn3_ctl_analysis import run_ctl
+
+from gn2.wqflask.wgcna.gn3_wgcna import run_wgcna
+from gn2.wqflask.snp_browser import snp_browser
+from gn2.wqflask.search_results import SearchResultPage
+from gn2.wqflask.export_traits import export_traits
+from gn2.wqflask.gsearch import GSearch
+from gn2.wqflask.update_search_results import GSearch as UpdateGSearch
+from gn2.wqflask.docs import Docs, update_text
+from gn2.wqflask.decorators import edit_access_required
+from gn2.wqflask.db_info import InfoPage
+
+from gn2.wqflask.oauth2 import client
+from gn2.wqflask.oauth2.client import no_token_get
+from gn2.wqflask.oauth2.request_utils import with_flash_error, with_flash_success
+
+from gn2.utility import temp_data
+from gn2.utility.tools import get_setting
+from gn2.utility.tools import TEMPDIR
+from gn2.utility.tools import USE_REDIS
+from gn2.utility.tools import REDIS_URL
+from gn2.utility.tools import GN_SERVER_URL
+from gn2.utility.tools import GN3_LOCAL_URL
+from gn2.utility.tools import GN_VERSION
+from gn2.utility.tools import JS_TWITTER_POST_FETCHER_PATH
+from gn2.utility.tools import JS_GUIX_PATH
+from gn2.utility.helper_functions import get_species_groups
+from gn2.utility.redis_tools import get_redis_conn
+
+import gn2.utility.hmac as hmac
+
+from gn2.base.webqtlConfig import TMPDIR
+from gn2.base.webqtlConfig import GENERATED_IMAGE_DIR
+
+from gn2.wqflask.database import database_connection
+
+import gn2.jobs.jobs as jobs
+
+from gn2.wqflask.oauth2.session import session_info
+from gn2.wqflask.oauth2.checks import user_logged_in
+
+from gn2.wqflask import requests as monad_requests
+
+Redis = get_redis_conn()
+
+
+@app.errorhandler(Exception)
+def handle_generic_exceptions(e):
+ import werkzeug
+ err_msg = str(e)
+ now = datetime.datetime.utcnow()
+ time_str = now.strftime('%l:%M%p UTC %b %d, %Y')
+ # get the stack trace and send it to the logger
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ formatted_lines = (f"{request.url} ({time_str}) \n"
+ f"{traceback.format_exc()}")
+ _message_templates = {
+ werkzeug.exceptions.NotFound: ("404: Not Found: "
+ f"{time_str}: {request.url}"),
+ werkzeug.exceptions.BadRequest: ("400: Bad Request: "
+ f"{time_str}: {request.url}"),
+ werkzeug.exceptions.RequestTimeout: ("408: Request Timeout: "
+ f"{time_str}: {request.url}")}
+ # Default to the lengthy stack trace!
+ app.logger.error(_message_templates.get(exc_type,
+ formatted_lines))
+ # Handle random animations
+ # Use a cookie to have one animation on refresh
+ animation = request.cookies.get(err_msg[:32])
+ if not animation:
+ animation = random.choice([fn for fn in os.listdir(
+ "./wqflask/static/gif/error") if fn.endswith(".gif")])
+
+ resp = make_response(render_template("error.html", message=err_msg,
+ stack={formatted_lines},
+ error_image=animation,
+ version=GN_VERSION))
+ resp.set_cookie(err_msg[:32], animation)
+ return resp
+
+
+@app.route("/authentication_needed")
+def no_access_page():
+ return render_template("new_security/not_authenticated.html")
+
+
+@app.route("/")
+def index_page():
+ anon_id = session_info()["anon_id"]
+ def __render__(colls):
+ return render_template("index_page.html", version=GN_VERSION,
+ gn_server_url=GN_SERVER_URL,
+ anon_collections=(
+ colls if user_logged_in() else []),
+ anon_id=anon_id)
+
+ return no_token_get(
+ f"auth/user/collections/{anon_id}/list").either(
+ lambda err: __render__([]),
+ __render__)
+
+
+@app.route("/tmp/<img_path>")
+def tmp_page(img_path):
+ initial_start_vars = request.form
+ imgfile = open(GENERATED_IMAGE_DIR + img_path, 'rb')
+ imgdata = imgfile.read()
+ imgB64 = base64.b64encode(imgdata)
+ bytesarray = array.array('B', imgB64)
+ return render_template("show_image.html",
+ img_base64=bytesarray)
+
+
+@app.route("/js/<path:filename>")
+def js(filename):
+ js_path = JS_GUIX_PATH
+ name = filename
+ if 'js_alt/' in filename:
+ js_path = js_path.replace('genenetwork2/javascript', 'javascript')
+ name = name.replace('js_alt/', '')
+ return send_from_directory(js_path, name)
+
+
+@app.route("/css/<path:filename>")
+def css(filename):
+ js_path = JS_GUIX_PATH
+ name = filename
+ if 'js_alt/' in filename:
+ js_path = js_path.replace('genenetwork2/javascript', 'javascript')
+ name = name.replace('js_alt/', '')
+ return send_from_directory(js_path, name)
+
+
+@app.route("/twitter/<path:filename>")
+def twitter(filename):
+ return send_from_directory(JS_TWITTER_POST_FETCHER_PATH, filename)
+
+
+@app.route("/search", methods=('GET',))
+def search_page():
+ result = None
+ if USE_REDIS:
+ key = "search_results:v1:" + \
+ json.dumps(request.args, sort_keys=True)
+ result = Redis.get(key)
+ if result:
+ result = pickle.loads(result)
+ result = SearchResultPage(request.args).__dict__
+ valid_search = result['search_term_exists']
+ if USE_REDIS and valid_search:
+ # Redis.set(key, pickle.dumps(result, pickle.HIGHEST_PROTOCOL))
+ Redis.expire(key, 60 * 60)
+
+ if valid_search:
+ return render_template("search_result_page.html", **result)
+ else:
+ return render_template("search_error.html")
+
+
+@app.route("/search_table", methods=('GET',))
+def search_page_table():
+ the_search = search_results.SearchResultPage(request.args)
+ current_page = server_side.ServerSideTable(
+ len(the_search.trait_list),
+ the_search.trait_list,
+ the_search.header_data_names,
+ request.args,
+ ).get_page()
+
+ return flask.jsonify(current_page)
+
+
+@app.route("/gsearch", methods=('GET',))
+def gsearchact():
+ result = GSearch(request.args).__dict__
+ type = request.args['type']
+ if type == "gene":
+ return render_template("gsearch_gene.html", **result)
+ elif type == "phenotype":
+ return render_template("gsearch_pheno.html", **result)
+
+
+@app.route("/gsearch_table", methods=('GET',))
+def gsearchtable():
+ gsearch_table_data = GSearch(request.args)
+ current_page = server_side.ServerSideTable(
+ gsearch_table_data.trait_count,
+ gsearch_table_data.trait_list,
+ gsearch_table_data.header_data_names,
+ request.args,
+ ).get_page()
+
+ return flask.jsonify(current_page)
+
+
+
+@app.route("/gnqna",methods =["POST","GET"])
+def gnqna():
+ if request.method == "POST":
+ try:
+ def __error__(resp):
+ return resp.json()
+ def __success__(resp):
+
+ return render_template("gnqa_answer.html",**resp.json())
+ return monad_requests.post(
+ urljoin(GN_SERVER_URL,
+ "llm/gnqna"),
+ json=dict(request.form),
+ ).then(
+ lambda resp: resp
+ ).either(
+ __error__, __success__)
+ except Exception as error:
+ return flask.jsonify({"error":str(error)})
+ return render_template("gnqa.html")
+
+
+@app.route("/gsearch_updating", methods=('POST',))
+def gsearch_updating():
+ result = UpdateGSearch(request.args).__dict__
+ return result['results']
+
+
+@app.route("/docedit")
+def docedit():
+ try:
+ if g.user_session.record['user_email_address'] == "zachary.a.sloan@gmail.com" or g.user_session.record['user_email_address'] == "labwilliams@gmail.com":
+ doc = Docs(request.args['entry'], request.args)
+ return render_template("docedit.html", **doc.__dict__)
+ else:
+ return "You shouldn't be here!"
+ except:
+ return "You shouldn't be here!"
+
+
+@app.route('/generated/<filename>')
+def generated_file(filename):
+ return send_from_directory(GENERATED_IMAGE_DIR, filename)
+
+
+@app.route("/help")
+def help():
+ doc = Docs("help", request.args)
+ return render_template("docs.html", **doc.__dict__)
+
+
+@app.route("/wgcna_setup", methods=('POST',))
+def wcgna_setup():
+ # We are going to get additional user input for the analysis
+ # Display them using the template
+ return render_template("wgcna_setup.html", **request.form)
+
+
+@app.route("/wgcna_results", methods=('POST',))
+def wcgna_results():
+ """call the gn3 api to get wgcna response data"""
+ results = run_wgcna(dict(request.form))
+ return render_template("gn3_wgcna_results.html", **results)
+
+
+@app.route("/ctl_setup", methods=('POST',))
+def ctl_setup():
+ # We are going to get additional user input for the analysis
+ # Display them using the template
+ return render_template("ctl_setup.html", **request.form)
+
+
+@app.route("/ctl_results", methods=["POST"])
+def ctl_results():
+ ctl_results = run_ctl(request.form)
+ return render_template("gn3_ctl_results.html", **ctl_results)
+
+
+@app.route("/ctl_network_files/<file_name>/<file_type>")
+def fetch_network_files(file_name, file_type):
+ file_path = f"{file_name}.{file_type}"
+
+ file_path = os.path.join("/tmp/", file_path)
+
+ return send_file(file_path)
+
+
+@app.route("/intro")
+def intro():
+ doc = Docs("intro", request.args)
+ return render_template("docs.html", **doc.__dict__)
+
+
+@app.route("/tutorials")
+def tutorials():
+ return render_template("tutorials.html")
+
+
+@app.route("/credits")
+def credits():
+ return render_template("credits.html")
+
+
+@app.route("/update_text", methods=('POST',))
+def update_page():
+ update_text(request.form)
+ doc = Docs(request.form['entry_type'], request.form)
+ return render_template("docs.html", **doc.__dict__)
+
+
+@app.route("/submit_trait")
+def submit_trait_form():
+ species_and_groups = get_species_groups()
+ return render_template(
+ "submit_trait.html",
+ species_and_groups=species_and_groups,
+ gn_server_url=GN_SERVER_URL,
+ version=GN_VERSION)
+
+
+@app.route("/create_temp_trait", methods=('POST',))
+def create_temp_trait():
+ doc = Docs("links")
+ return render_template("links.html", **doc.__dict__)
+
+
+@app.route('/export_trait_excel', methods=('POST',))
+def export_trait_excel():
+ """Excel file consisting of the sample data from the trait data and analysis page"""
+ trait_name, sample_data = export_trait_data.export_sample_table(
+ request.form)
+ app.logger.info(request.url)
+ buff = io.BytesIO()
+ workbook = xlsxwriter.Workbook(buff, {'in_memory': True})
+ worksheet = workbook.add_worksheet()
+ for i, row in enumerate(sample_data):
+ for j, column in enumerate(row):
+ worksheet.write(i, j, row[j])
+ workbook.close()
+ excel_data = buff.getvalue()
+ buff.close()
+
+ return Response(excel_data,
+ mimetype='application/vnd.ms-excel',
+ headers={"Content-Disposition": "attachment;filename=" + trait_name + ".xlsx"})
+
+
+@app.route('/export_trait_csv', methods=('POST',))
+def export_trait_csv():
+ """CSV file consisting of the sample data from the trait data and analysis page"""
+ trait_name, sample_data = export_trait_data.export_sample_table(
+ request.form)
+
+ buff = io.StringIO()
+ writer = csv.writer(buff)
+ for row in sample_data:
+ writer.writerow(row)
+ csv_data = buff.getvalue()
+ buff.close()
+
+ return Response(csv_data,
+ mimetype='text/csv',
+ headers={"Content-Disposition": "attachment;filename=" + trait_name + ".csv"})
+
+
+@app.route('/export_traits_csv', methods=('POST',))
+def export_traits_csv():
+ """CSV file consisting of the traits from the search result page"""
+ file_list = export_traits(request.form, "metadata")
+
+ if len(file_list) > 1:
+ now = datetime.datetime.now()
+ time_str = now.strftime('%H:%M_%d%B%Y')
+ filename = "export_{}".format(time_str)
+ memory_file = io.BytesIO()
+ with ZipFile(memory_file, mode='w', compression=ZIP_DEFLATED) as zf:
+ for the_file in file_list:
+ zf.writestr(the_file[0], the_file[1])
+
+ memory_file.seek(0)
+
+ return send_file(memory_file, attachment_filename=filename + ".zip", as_attachment=True)
+ else:
+ return Response(file_list[0][1],
+ mimetype='text/csv',
+ headers={"Content-Disposition": "attachment;filename=" + file_list[0][0]})
+
+
+@app.route('/export_collection', methods=('POST',))
+def export_collection_csv():
+ """CSV file consisting of trait list so collections can be exported/shared"""
+ out_file = export_traits(request.form, "collection")
+ return Response(out_file[1],
+ mimetype='text/csv',
+ headers={"Content-Disposition": "attachment;filename=" + out_file[0] + ".csv"})
+
+
+@app.route('/export_perm_data', methods=('POST',))
+def export_perm_data():
+ """CSV file consisting of the permutation data for the mapping results"""
+ perm_info = json.loads(request.form['perm_info'])
+
+ now = datetime.datetime.now()
+ time_str = now.strftime('%H:%M_%d%B%Y')
+
+ file_name = "Permutation_" + \
+ perm_info['num_perm'] + "_" + perm_info['trait_name'] + "_" + time_str
+
+ the_rows = [
+ ["#Permutation Test"],
+ ["#File_name: " + file_name],
+ ["#Metadata: From GeneNetwork.org"],
+ ["#Trait_ID: " + perm_info['trait_name']],
+ ["#Trait_description: " + perm_info['trait_description']],
+ ["#N_permutations: " + str(perm_info['num_perm'])],
+ ["#Cofactors: " + perm_info['cofactors']],
+ ["#N_cases: " + str(perm_info['n_samples'])],
+ ["#N_genotypes: " + str(perm_info['n_genotypes'])],
+ ["#Genotype_file: " + perm_info['genofile']],
+ ["#Units_linkage: " + perm_info['units_linkage']],
+ ["#Permutation_stratified_by: "
+ + ", ".join([str(cofactor) for cofactor in perm_info['strat_cofactors']])],
+ ["#RESULTS_1: Suggestive LRS(p=0.63) = "
+ + str(np.percentile(np.array(perm_info['perm_data']), 67))],
+ ["#RESULTS_2: Significant LRS(p=0.05) = " + str(
+ np.percentile(np.array(perm_info['perm_data']), 95))],
+ ["#RESULTS_3: Highly Significant LRS(p=0.01) = " + str(
+ np.percentile(np.array(perm_info['perm_data']), 99))],
+ ["#Comment: Results sorted from low to high peak linkage"]
+ ]
+
+ buff = io.StringIO()
+ writer = csv.writer(buff)
+ writer.writerows(the_rows)
+ for item in perm_info['perm_data']:
+ writer.writerow([item])
+ csv_data = buff.getvalue()
+ buff.close()
+
+ return Response(csv_data,
+ mimetype='text/csv',
+ headers={"Content-Disposition": "attachment;filename=" + file_name + ".csv"})
+
+
+@app.route("/show_temp_trait", methods=('POST',))
+def show_temp_trait_page():
+ with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor:
+ user_id = ((g.user_session.record.get(b"user_id") or b"").decode("utf-8")
+ or g.user_session.record.get("user_id") or "")
+ template_vars = show_trait.ShowTrait(cursor,
+ user_id=user_id,
+ kw=request.form)
+ template_vars.js_data = json.dumps(template_vars.js_data,
+ default=json_default_handler,
+ indent=" ")
+ return redirect(url_for("show_trait_page", dataset=template_vars.dataset.name, trait_id=template_vars.trait_id))
+
+
+@app.route("/show_trait")
+def show_trait_page():
+ def __show_trait__(privileges_data):
+ assert len(privileges_data) == 1
+ privileges_data = privileges_data[0]
+ trait_privileges = tuple(item for item in privileges_data["privileges"])
+ with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor:
+
+ user_id = ((g.user_session.record.get(b"user_id") or b"").decode("utf-8")
+ or g.user_session.record.get("user_id") or "")
+ template_vars = show_trait.ShowTrait(cursor,
+ user_id=user_id,
+ kw=request.args)
+ template_vars.js_data = json.dumps(template_vars.js_data,
+ default=json_default_handler,
+ indent=" ")
+ return render_template(
+ "show_trait.html",
+ **{
+ **template_vars.__dict__,
+ "user": privileges_data["user"],
+ "trait_privileges": trait_privileges,
+ "resource_id": privileges_data["resource_id"]
+ })
+ dataset = request.args["dataset"]
+ trait_id = request.args["trait_id"]
+
+ return client.post(
+ "auth/data/authorisation",
+ json={
+ "traits": [f"{dataset}::{trait_id}"]
+ }).either(with_flash_error(render_template("show_trait_error.html")),
+ __show_trait__)
+
+
+@app.route("/heatmap", methods=('POST',))
+def heatmap_page():
+ start_vars = request.form
+ temp_uuid = uuid.uuid4()
+
+ traits = [trait.strip() for trait in start_vars['trait_list'].split(',')]
+ with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor:
+ if traits[0] != "":
+ version = "v5"
+ key = "heatmap:{}:".format(
+ version) + json.dumps(start_vars, sort_keys=True)
+ result = Redis.get(key)
+
+ if result:
+ result = pickle.loads(result)
+
+ else:
+ template_vars = heatmap.Heatmap(cursor, request.form, temp_uuid)
+ template_vars.js_data = json.dumps(template_vars.js_data,
+ default=json_default_handler,
+ indent=" ")
+
+ result = template_vars.__dict__
+
+ pickled_result = pickle.dumps(result, pickle.HIGHEST_PROTOCOL)
+ Redis.set(key, pickled_result)
+ Redis.expire(key, 60 * 60)
+ rendered_template = render_template("heatmap.html", **result)
+
+ else:
+ rendered_template = render_template(
+ "empty_collection.html", **{'tool': 'Heatmap'})
+
+ return rendered_template
+
+
+@app.route("/bnw_page", methods=('POST',))
+def bnw_page():
+ start_vars = request.form
+
+ traits = [trait.strip() for trait in start_vars['trait_list'].split(',')]
+ if traits[0] != "":
+ template_vars = send_to_bnw.SendToBNW(request.form)
+
+ result = template_vars.__dict__
+ rendered_template = render_template("bnw_page.html", **result)
+ else:
+ rendered_template = render_template(
+ "empty_collection.html", **{'tool': 'BNW'})
+
+ return rendered_template
+
+
+@app.route("/webgestalt_page", methods=('POST',))
+def webgestalt_page():
+ start_vars = request.form
+
+ traits = [trait.strip() for trait in start_vars['trait_list'].split(',')]
+ if traits[0] != "":
+ template_vars = send_to_webgestalt.SendToWebGestalt(request.form)
+
+ result = template_vars.__dict__
+ rendered_template = render_template("webgestalt_page.html", **result)
+ else:
+ rendered_template = render_template(
+ "empty_collection.html", **{'tool': 'WebGestalt'})
+
+ return rendered_template
+
+
+@app.route("/geneweaver_page", methods=('POST',))
+def geneweaver_page():
+ start_vars = request.form
+
+ traits = [trait.strip() for trait in start_vars['trait_list'].split(',')]
+ if traits[0] != "":
+ template_vars = send_to_geneweaver.SendToGeneWeaver(request.form)
+
+ result = template_vars.__dict__
+ rendered_template = render_template("geneweaver_page.html", **result)
+ else:
+ rendered_template = render_template(
+ "empty_collection.html", **{'tool': 'GeneWeaver'})
+
+ return rendered_template
+
+
+@app.route("/comparison_bar_chart", methods=('POST',))
+def comp_bar_chart_page():
+ start_vars = request.form
+
+ traits = [trait.strip() for trait in start_vars['trait_list'].split(',')]
+ if traits[0] != "":
+ template_vars = comparison_bar_chart.ComparisonBarChart(request.form)
+ template_vars.js_data = json.dumps(template_vars.js_data,
+ default=json_default_handler,
+ indent=" ")
+
+ result = template_vars.__dict__
+ rendered_template = render_template(
+ "comparison_bar_chart.html", **result)
+ else:
+ rendered_template = render_template(
+ "empty_collection.html", **{'tool': 'Comparison Bar Chart'})
+
+ return rendered_template
+
+
+@app.route("/mapping_results_container")
+def mapping_results_container_page():
+ return render_template("mapping_results_container.html")
+
+
+@app.route("/loading", methods=('POST',))
+def loading_page():
+ initial_start_vars = request.form
+ start_vars_container = {}
+ n_samples = 0 # ZS: So it can be displayed on loading page
+ if 'wanted_inputs' in initial_start_vars:
+ wanted = initial_start_vars['wanted_inputs'].split(",")
+ start_vars = {}
+ for key, value in list(initial_start_vars.items()):
+ if key in wanted:
+ start_vars[key] = value
+
+ sample_vals_dict = json.loads(start_vars['sample_vals'])
+ if 'n_samples' in start_vars:
+ n_samples = int(start_vars['n_samples'])
+ else:
+ if 'group' in start_vars:
+ dataset = create_dataset(
+ start_vars['dataset'], group_name=start_vars['group'])
+ else:
+ dataset = create_dataset(start_vars['dataset'])
+ start_vars['trait_name'] = start_vars['trait_id']
+ if dataset.type == "Publish":
+ start_vars['trait_name'] = f"{dataset.group.code}_{start_vars['trait_name']}"
+ samples = dataset.group.samplelist
+ if 'genofile' in start_vars:
+ if start_vars['genofile'] != "":
+ genofile_string = start_vars['genofile']
+ dataset.group.genofile = genofile_string.split(":")[0]
+ genofile_samples = run_mapping.get_genofile_samplelist(
+ dataset)
+ if len(genofile_samples) > 1:
+ samples = genofile_samples
+
+ for sample in samples:
+ if sample in sample_vals_dict:
+ if sample_vals_dict[sample] != "x":
+ n_samples += 1
+
+ start_vars['n_samples'] = n_samples
+ start_vars['vals_hash'] = generate_hash_of_string(
+ str(sample_vals_dict))
+ if start_vars['dataset'] != "Temp": # Currently can't get diff for temp traits
+ start_vars['vals_diff'] = get_diff_of_vals(sample_vals_dict, str(
+ start_vars['trait_id'] + ":" + str(start_vars['dataset'])))
+
+ start_vars['wanted_inputs'] = initial_start_vars['wanted_inputs']
+
+ start_vars_container['start_vars'] = start_vars
+ else:
+ start_vars_container['start_vars'] = initial_start_vars
+
+ rendered_template = render_template("loading.html", **start_vars_container)
+
+ return rendered_template
+
+
+@app.route("/run_mapping", methods=('POST',))
+@app.route("/run_mapping/<path:hash_of_inputs>")
+def mapping_results_page(hash_of_inputs=None):
+ if hash_of_inputs:
+ initial_start_vars = json.loads(Redis.get(hash_of_inputs))
+ initial_start_vars['hash_of_inputs'] = hash_of_inputs
+ else:
+ initial_start_vars = request.form
+
+ # Get hash of inputs (as JSON) for sharing results
+ inputs_json = json.dumps(initial_start_vars, sort_keys=True)
+ dhash = hashlib.md5()
+ dhash.update(inputs_json.encode())
+ hash_of_inputs = dhash.hexdigest()
+
+ # Just store for one hour on initial load; will be stored for longer if user clicks Share
+ Redis.set(hash_of_inputs, inputs_json, ex=60*60*24*30)
+
+ temp_uuid = initial_start_vars['temp_uuid']
+ wanted = (
+ 'trait_id',
+ 'dataset',
+ 'group',
+ 'species',
+ 'samples',
+ 'vals',
+ 'sample_vals',
+ 'vals_hash',
+ 'first_run',
+ 'output_files',
+ 'geno_db_exists',
+ 'method',
+ 'mapping_results_path',
+ 'trimmed_markers',
+ 'selected_chr',
+ 'chromosomes',
+ 'mapping_scale',
+ 'plotScale',
+ 'score_type',
+ 'suggestive',
+ 'significant',
+ 'num_perm',
+ 'permCheck',
+ 'perm_strata',
+ 'categorical_vars',
+ 'perm_output',
+ 'num_bootstrap',
+ 'bootCheck',
+ 'bootstrap_results',
+ 'LRSCheck',
+ 'covariates',
+ 'maf',
+ 'use_loco',
+ 'manhattan_plot',
+ 'color_scheme',
+ 'manhattan_single_color',
+ 'control_marker',
+ 'do_control',
+ 'genofile',
+ 'genofile_string',
+ 'pair_scan',
+ 'startMb',
+ 'endMb',
+ 'graphWidth',
+ 'lrsMax',
+ 'additiveCheck',
+ 'showSNP',
+ 'showHomology',
+ 'showGenes',
+ 'viewLegend',
+ 'haplotypeAnalystCheck',
+ 'mapmethod_rqtl',
+ 'mapmodel_rqtl',
+ 'temp_trait',
+ 'n_samples',
+ 'transform',
+ 'hash_of_inputs',
+ 'dataid'
+ )
+ start_vars = {}
+ for key, value in list(initial_start_vars.items()):
+ if key in wanted:
+ start_vars[key] = value
+
+ start_vars['hash_of_inputs'] = hash_of_inputs
+
+ # Store trait sample data in Redis, so additive effect scatterplots can include edited values
+ dhash = hashlib.md5()
+ dhash.update(start_vars['sample_vals'].encode())
+ samples_hash = dhash.hexdigest()
+ Redis.set(samples_hash, start_vars['sample_vals'], ex=7*24*60*60)
+ start_vars['dataid'] = samples_hash
+
+ version = "v3"
+ key = "mapping_results:{}:".format(
+ version) + json.dumps(start_vars, sort_keys=True)
+ result = None # Just for testing
+
+ if result:
+ result = pickle.loads(result)
+ else:
+ template_vars = run_mapping.RunMapping(start_vars, temp_uuid)
+ if template_vars.no_results:
+ raise NoMappingResultsError(
+ start_vars["trait_id"], start_vars["dataset"], start_vars["method"])
+
+ if not template_vars.pair_scan:
+ template_vars.js_data = json.dumps(template_vars.js_data,
+ default=json_default_handler,
+ indent=" ")
+
+ result = template_vars.__dict__
+
+ if result['pair_scan']:
+ rendered_template = render_template(
+ "pair_scan_results.html", **result)
+ else:
+ gn1_template_vars = display_mapping_results.DisplayMappingResults(
+ result).__dict__
+
+ rendered_template = render_template(
+ "mapping_results.html", **gn1_template_vars)
+
+ return rendered_template
+
+@app.route("/cache_mapping_inputs", methods=('POST',))
+def cache_mapping_inputs():
+ cache_id = request.form.get("inputs_hash")
+ inputs_json = Redis.get(cache_id)
+ Redis.set(cache_id, inputs_json)
+
+ return "Success"
+
+@app.route("/export_mapping_results", methods=('POST',))
+def export_mapping_results():
+ file_path = request.form.get("results_path")
+ results_csv = open(file_path, "r").read()
+ response = Response(results_csv,
+ mimetype='text/csv',
+ headers={"Content-Disposition": "attachment;filename=" + os.path.basename(file_path)})
+
+ return response
+
+
+@app.route("/export_corr_matrix", methods=('POST',))
+def export_corr_matrix():
+ file_path = request.form.get("export_filepath")
+ file_name = request.form.get("export_filename")
+ results_csv = open(file_path, "r").read()
+ response = Response(results_csv,
+ mimetype='text/csv',
+ headers={"Content-Disposition": "attachment;filename=" + file_name + ".csv"})
+
+ return response
+
+
+@app.route("/export", methods=('POST',))
+def export():
+ svg_xml = request.form.get("data", "Invalid data")
+ filename = request.form.get("filename", "manhattan_plot_snp")
+ response = Response(svg_xml, mimetype="image/svg+xml")
+ response.headers["Content-Disposition"] = "attachment; filename=%s" % filename
+ return response
+
+
+@app.route("/export_pdf", methods=('POST',))
+def export_pdf():
+ import cairosvg
+ svg_xml = request.form.get("data", "Invalid data")
+ filename = request.form.get("filename", "interval_map_pdf")
+ pdf_file = cairosvg.svg2pdf(bytestring=svg_xml)
+ response = Response(pdf_file, mimetype="application/pdf")
+ response.headers["Content-Disposition"] = "attachment; filename=%s" % filename
+ return response
+
+
+@app.route("/network_graph", methods=('POST',))
+def network_graph_page():
+ start_vars = request.form
+ traits = [trait.strip() for trait in start_vars['trait_list'].split(',')]
+ if traits[0] != "":
+ template_vars = network_graph.NetworkGraph(start_vars)
+ template_vars.js_data = json.dumps(template_vars.js_data,
+ default=json_default_handler,
+ indent=" ")
+
+ return render_template("network_graph.html", **template_vars.__dict__)
+ else:
+ return render_template("empty_collection.html", **{'tool': 'Network Graph'})
+
+def __handle_correlation_error__(exc):
+ return render_template(
+ "correlation_error_page.html",
+ error = {
+ "error-type": {
+ "WrongCorrelationType": "Wrong Correlation Type"
+ }[type(exc).__name__],
+ "error-message": exc.args[0]
+ })
+
+@app.route("/corr_compute", methods=('POST', 'GET'))
+def corr_compute_page():
+ with Redis.from_url(REDIS_URL, decode_responses=True) as rconn:
+ if request.method == "POST":
+ request_received = datetime.datetime.utcnow()
+ filename=hmac.hmac_creation(f"request_form_{request_received.isoformat()}")
+ filepath = f"{TMPDIR}{filename}"
+ with open(filepath, "wb") as pfile:
+ pickle.dump(request.form, pfile, protocol=pickle.HIGHEST_PROTOCOL)
+ job_id = jobs.queue(
+ rconn, {
+ "command": [
+ sys.executable, "-m", "scripts.corr_compute", filepath,
+ g.user_session.user_id],
+ "request_received_time": request_received.isoformat(),
+ "status": "queued"
+ })
+ jobs.run(job_id, REDIS_URL)
+
+ return redirect(url_for("corr_compute_page", job_id=str(job_id)))
+
+ job = jobs.job(
+ rconn, UUID(request.args.get("job_id"))).maybe(
+ {}, lambda the_job: the_job)
+
+ if jobs.completed_successfully(job):
+ output = json.loads(job.get("stdout", "{}"))
+ return render_template("correlation_page.html", **output)
+
+ if jobs.completed_erroneously(job):
+ try:
+ error_output = {
+ "error-type": "ComputeError",
+ "error-message": "There was an error computing the correlations",
+ **json.loads(job.get("stdout") or "{}"),
+ "stderr-output": job.get("stderr", "").split("\n")
+ }
+ return render_template(
+ "correlation_error_page.html", error=error_output)
+ except json.decoder.JSONDecodeError as jde:
+ raise Exception(f"STDOUT: {job.get('stdout')}") from jde
+
+ return render_template("loading_corrs.html")
+
+
+@app.route("/corr_matrix", methods=('POST',))
+def corr_matrix_page():
+ start_vars = request.form
+ traits = [trait.strip() for trait in start_vars['trait_list'].split(',')]
+ if len(traits) > 1:
+ template_vars = show_corr_matrix.CorrelationMatrix(start_vars)
+ template_vars.js_data = json.dumps(template_vars.js_data,
+ default=json_default_handler,
+ indent=" ")
+
+ return render_template("correlation_matrix.html", **template_vars.__dict__)
+ else:
+ return render_template("empty_collection.html", **{'tool': 'Correlation Matrix'})
+
+
+@app.route("/corr_scatter_plot")
+def corr_scatter_plot_page():
+ template_vars = corr_scatter_plot.CorrScatterPlot(request.args)
+ template_vars.js_data = json.dumps(template_vars.js_data,
+ default=json_default_handler,
+ indent=" ")
+ return render_template("corr_scatterplot.html", **template_vars.__dict__)
+
+
+@app.route("/snp_browser", methods=('GET',))
+def snp_browser_page():
+ with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor:
+ template_vars = snp_browser.SnpBrowser(cursor, request.args)
+ return render_template("snp_browser.html", **template_vars.__dict__)
+
+
+@app.route("/db_info", methods=('GET',))
+def db_info_page():
+ template_vars = InfoPage(request.args)
+
+ return render_template("info_page.html", **template_vars.__dict__)
+
+
+@app.route("/snp_browser_table", methods=('GET',))
+def snp_browser_table():
+ with database_connection(get_setting("SQL_URI")) as conn, conn.cursor() as cursor:
+ snp_table_data = snp_browser.SnpBrowser(cursor, request.args)
+ current_page = server_side.ServerSideTable(
+ snp_table_data.rows_count,
+ snp_table_data.table_rows,
+ snp_table_data.header_data_names,
+ request.args,
+ ).get_page()
+
+ return flask.jsonify(current_page)
+
+
+@app.route("/tutorial/WebQTLTour", methods=('GET',))
+def tutorial_page():
+ # ZS: Currently just links to GN1
+ return redirect("http://gn1.genenetwork.org/tutorial/WebQTLTour/")
+
+
+@app.route("/tutorial/security", methods=('GET',))
+def security_tutorial_page():
+ # ZS: Currently just links to GN1
+ return render_template("admin/security_help.html")
+
+
+@app.route("/submit_bnw", methods=('POST',))
+def submit_bnw():
+ return render_template("empty_collection.html", **{'tool': 'Correlation Matrix'})
+
+# Take this out or secure it before putting into production
+
+
+@app.route("/get_temp_data")
+def get_temp_data():
+ temp_uuid = request.args['key']
+ return flask.jsonify(temp_data.TempData(temp_uuid).get_all())
+
+
+@app.route("/browser_input", methods=('GET',))
+def browser_inputs():
+ """ Returns JSON from tmp directory for the purescript genome browser"""
+
+ filename = request.args['filename']
+
+ with open("{}/gn2/".format(TEMPDIR) + filename + ".json", "r") as the_file:
+ file_contents = json.load(the_file)
+
+ return flask.jsonify(file_contents)
+
+
+def json_default_handler(obj):
+ """Based on http://stackoverflow.com/a/2680060/1175849"""
+ # Handle datestamps
+ if hasattr(obj, 'isoformat'):
+ return obj.isoformat()
+ # Handle integer keys for dictionaries
+ elif isinstance(obj, int) or isinstance(obj, uuid.UUID):
+ return str(obj)
+ # Handle custom objects
+ if hasattr(obj, '__dict__'):
+ return obj.__dict__
+ else:
+ raise TypeError('Object of type %s with value of %s is not JSON serializable' % (
+ type(obj), repr(obj)))
+
+
+@app.route("/admin/data-sample/diffs/")
+@edit_access_required
+def display_diffs_admin():
+ TMPDIR = current_app.config.get("TMPDIR")
+ DIFF_DIR = f"{TMPDIR}/sample-data/diffs"
+ files = []
+ if os.path.exists(DIFF_DIR):
+ files = os.listdir(DIFF_DIR)
+ files = filter(lambda x: not(x.endswith((".approved", ".rejected"))),
+ files)
+ return render_template("display_files_admin.html",
+ files=files)
+
+
+@app.route("/user/data-sample/diffs/")
+def display_diffs_users():
+ TMPDIR = current_app.config.get("TMPDIR")
+ DIFF_DIR = f"{TMPDIR}/sample-data/diffs"
+ files = []
+ author = g.user_session.record.get(b'user_name').decode("utf-8")
+ if os.path.exists(DIFF_DIR):
+ files = os.listdir(DIFF_DIR)
+ files = filter(lambda x: not(x.endswith((".approved", ".rejected")))
+ and author in x,
+ files)
+ return render_template("display_files_user.html",
+ files=files)
+
+
+@app.route("/genewiki/<symbol>")
+def display_generif_page(symbol):
+ """Fetch GeneRIF metadata from GN3 and display it"""
+ entries = requests.get(
+ urljoin(
+ GN3_LOCAL_URL,
+ f"/api/metadata/genewiki/{symbol}"
+ )
+ ).json()
+ return render_template(
+ "generif.html",
+ symbol=symbol,
+ entries=entries
+ )
+
+
+@app.route("/datasets/<name>", methods=('GET',))
+def get_dataset(name):
+ metadata = requests.get(
+ urljoin(
+ GN3_LOCAL_URL,
+ f"/api/metadata/datasets/{name}")
+ ).json()
+ return render_template(
+ "dataset.html",
+ name=name,
+ dataset=metadata
+ )
+
+@app.route("/datasets/search", methods=('POST',))
+def search_for_dataset():
+ search = request.form.get('search')
+ return render_template(
+ "metadata/dataset_search_results.html",
+ results=search
+ )
+
+@app.route("/publications/<name>", methods=('GET',))
+def get_publication(name):
+ metadata = requests.get(
+ urljoin(
+ GN3_LOCAL_URL,
+ f"/api/metadata/publications/{name}")
+ ).json()
+ return render_template(
+ "publication.html",
+ metadata=metadata,
+ )
+
+
+@app.route("/phenotypes/<name>", methods=('GET',))
+@app.route("/phenotypes/<group>/<name>", methods=('GET',))
+def get_phenotype(name, group=None):
+ if group:
+ name = f"{group}_{name}"
+ metadata = requests.get(
+ urljoin(
+ GN3_LOCAL_URL,
+ f"/api/metadata/phenotypes/{name}")
+ ).json()
+ return render_template(
+ "phenotype.html",
+ metadata=metadata,
+ )
+
+
+@app.route("/genotypes/<name>", methods=('GET',))
+def get_genotype(name):
+ metadata = requests.get(
+ urljoin(
+ GN3_LOCAL_URL,
+ f"/api/metadata/genotypes/{name}")
+ ).json()
+ return render_template(
+ "genotype.html",
+ metadata=metadata,
+ )
+
+@app.route("/case-attribute/<int:inbredset_id>/edit", methods=["GET", "POST"])
+def edit_case_attributes(inbredset_id: int) -> Response:
+ """
+ Edit the case-attributes for InbredSet group identified by `inbredset_id`.
+ """
+ if request.method == "POST":
+ form = request.form
+ def __process_data__(acc, item):
+ _new, strain, calabel = tuple(val.strip() for val in item[0].split(":"))
+ old_row = acc.get(strain, {})
+ return {
+ **acc,
+ strain: {
+ **old_row, "case-attributes": {
+ **old_row.get("case-attributes", {}),
+ calabel: item[1]
+ }
+ }
+ }
+
+ edit_case_attributes_page = redirect(url_for(
+ "edit_case_attributes", inbredset_id=inbredset_id))
+ token = session_info()["user"]["token"].either(
+ lambda err: err, lambda tok: tok["access_token"])
+ def flash_success(resp):
+ def __succ__(remote_resp):
+ flash(f"Success: {remote_resp.json()['message']}", "alert-success")
+ return resp
+ return __succ__
+ return monad_requests.post(
+ urljoin(
+ current_app.config["GN_SERVER_URL"],
+ f"/api/case-attribute/{inbredset_id}/edit"),
+ json={
+ "edit-data": reduce(__process_data__, form.items(), {})
+ },
+ headers={
+ "Authorization": f"Bearer {token}"}).either(
+ with_flash_error(edit_case_attributes_page),
+ flash_success(edit_case_attributes_page))
+
+ def __fetch_strains__(inbredset_group):
+ return monad_requests.get(urljoin(
+ current_app.config["GN_SERVER_URL"],
+ f"/api/case-attribute/{inbredset_id}/strains")).then(
+ lambda resp: {**inbredset_group, "strains": resp.json()})
+
+ def __fetch_names__(strains):
+ return monad_requests.get(urljoin(
+ current_app.config["GN_SERVER_URL"],
+ f"/api/case-attribute/{inbredset_id}/names")).then(
+ lambda resp: {**strains, "case_attribute_names": resp.json()})
+
+ def __fetch_values__(canames):
+ return monad_requests.get(urljoin(
+ current_app.config["GN_SERVER_URL"],
+ f"/api/case-attribute/{inbredset_id}/values")).then(
+ lambda resp: {**canames, "case_attribute_values": {
+ value["StrainName"]: value for value in resp.json()}})
+
+ return monad_requests.get(urljoin(
+ current_app.config["GN_SERVER_URL"],
+ f"/api/case-attribute/{inbredset_id}")).then(
+ lambda resp: {"inbredset_group": resp.json()}).then(
+ __fetch_strains__).then(__fetch_names__).then(
+ __fetch_values__).either(
+ lambda err: err, ## TODO: Handle error better
+ lambda values: render_template(
+ "edit_case_attributes.html", inbredset_id=inbredset_id, **values))
+
+@app.route("/case-attribute/<int:inbredset_id>/list-diffs", methods=["GET"])
+def list_case_attribute_diffs(inbredset_id: int) -> Response:
+ """List any diffs awaiting review."""
+ return monad_requests.get(urljoin(
+ current_app.config["GN_SERVER_URL"],
+ f"/api/case-attribute/{inbredset_id}/diff/list")).then(
+ lambda resp: resp.json()).either(
+ lambda err: render_template(
+ "list_case_attribute_diffs_error.html",
+ inbredset_id=inbredset_id,
+ error=err),
+ lambda diffs: render_template(
+ "list_case_attribute_diffs.html",
+ inbredset_id=inbredset_id,
+ diffs=diffs))
+
+@app.route("/case-attribute/<int:inbredset_id>/diff/<int:diff_id>/view", methods=["GET"])
+def view_diff(inbredset_id:int, diff_id: int) -> Response:
+ """View the pending diff."""
+ token = session_info()["user"]["token"].either(
+ lambda err: err, lambda tok: tok["access_token"])
+ return monad_requests.get(
+ urljoin(current_app.config["GN_SERVER_URL"],
+ f"/api/case-attribute/{inbredset_id}/diff/{diff_id}/view"),
+ headers={"Authorization": f"Bearer {token}"}).then(
+ lambda resp: resp.json()).either(
+ lambda err: render_template(
+ "view_case_attribute_diff_error.html", error=err.json()),
+ lambda diff: render_template(
+ "view_case_attribute_diff.html", diff=diff))
+
+@app.route("/case-attribute/diff/approve-reject", methods=["POST"])
+def approve_reject_diff() -> Response:
+ """Approve/Reject the diff."""
+ try:
+ form = request.form
+ action = form["action"]
+ assert action in ("approve", "reject")
+ diff_data = json.loads(form["diff_data"])
+ diff_data = {
+ **diff_data,
+ "created": datetime.datetime.fromisoformat(diff_data["created"])}
+ inbredset_id = diff_data["inbredset_id"]
+ filename = (
+ f"{inbredset_id}:::{diff_data['user_id']}:::"
+ f"{diff_data['created'].isoformat()}.json")
+
+ list_diffs_page = url_for("list_case_attribute_diffs",
+ inbredset_id=inbredset_id)
+ token = session_info()["user"]["token"].either(
+ lambda err: err, lambda tok: tok["access_token"])
+ def __error__(resp):
+ error = resp.json()
+ flash((f"{resp.status_code} {error['error']}: "
+ f"{error['error_description']}"),
+ "alert-danger")
+ return redirect(list_diffs_page)
+ def __success__(results):
+ flash(results["message"], "alert-success")
+ return redirect(list_diffs_page)
+ return monad_requests.post(
+ urljoin(current_app.config["GN_SERVER_URL"],
+ f"/api/case-attribute/{action}/{filename}"),
+ headers={"Authorization": f"Bearer {token}"}).then(
+ lambda resp: resp.json()
+ ).either(
+ __error__, __success__)
+ except AssertionError as _ae:
+ flash("Invalid action! Expected either 'approve' or 'reject'.",
+ "alert-danger")
+ return redirect(url_for("view_diff",
+ inbredset_id=inbredset_id,
+ diff_id=form["diff_id"]))