diff --git a/MobiDetailsApp/ajax.py b/MobiDetailsApp/ajax.py index f1e9adf8..73539a92 100644 --- a/MobiDetailsApp/ajax.py +++ b/MobiDetailsApp/ajax.py @@ -91,16 +91,16 @@ def litvar(): # print(article) if article['authors'][0]: pmid = int(article['pmid']) - pubmed_info[pmid] = {} - pubmed_info[pmid]['title'] = article['title'] - pubmed_info[pmid]['journal'] = article['journal'] - pubmed_info[pmid]['year'] = article['year'] - pubmed_info[pmid]['author'] = article['authors'][0] + pubmed_info[pmid] = { + 'title': article['title'], + 'journal': article['journal'], + 'year': article['year'], + 'author': article['authors'][0], + } except Exception: for pubmed_id in litvar_data[0]['pmids']: pmid = int(pubmed_id) - pubmed_info[pmid] = {} - pubmed_info[pmid]['title'] = '' + pubmed_info[pmid] = {'title': ''} return render_template( 'ajax/litvar.html', urls=md_utilities.urls, @@ -118,9 +118,8 @@ def litvar(): @bp.route('/litvar2', methods=['POST']) def litvar2(): - match_rsid = re.search(r'^(rs\d+)$', request.form['rsid']) - if match_rsid: - rsid = match_rsid.group(1) + if match_rsid := re.search(r'^(rs\d+)$', request.form['rsid']): + rsid = match_rsid[1] # if re.search(r'^rs\d+$', request.form['rsid']): header = md_utilities.api_agent # rsid = request.form['rsid'] @@ -186,21 +185,20 @@ def litvar2(): if 'authors' in article and \ len(article['authors']) > 0: pmid = int(article['pmid']) - pubmed_info[pmid] = {} - pubmed_info[pmid]['title'] = article['title'] - pubmed_info[pmid]['journal'] = article['journal'] - pubmed_info[pmid]['year'] = article['year'] - pubmed_info[pmid]['author'] = article['authors'][0] + pubmed_info[pmid] = { + 'title': article['title'], + 'journal': article['journal'], + 'year': article['year'], + 'author': article['authors'][0], + } except Exception: for pubmed_id in litvar_data['pmids']: pmid = int(pubmed_id) - pubmed_info[pmid] = {} - pubmed_info[pmid]['title'] = '' + pubmed_info[pmid] = {'title': ''} if not pubmed_info: for pubmed_id in litvar_data['pmids']: pmid = int(pubmed_id) - pubmed_info[pmid] = {} - pubmed_info[pmid]['title'] = '' + pubmed_info[pmid] = {'title': ''} # get litvar direct link of rsid litvar_sensor_url = "{0}{1}".format( md_utilities.urls['ncbi_litvar_apiv2_sensor'], rsid @@ -256,9 +254,9 @@ def defgen(): match_varid = re.search(r'^(\d+)$', request.form['vfid']) if match_varid and \ match_genome: - variant_id = match_varid.group(1) + variant_id = match_varid[1] # genome = request.form['genome'] - genome = match_genome.group(1) + genome = match_genome[1] db = get_db() curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) # get all variant_features and gene info @@ -274,19 +272,28 @@ def defgen(): (variant_id, genome) ) vf = curs.fetchone() - file_content = "GENE;VARIANT;A_ENREGISTRER;ETAT;RESULTAT;VARIANT_P;\ + rsid = 'rs{0}'.format(vf['dbsnp_id']) if vf['dbsnp_id'] else '' + file_content = ( + "GENE;VARIANT;A_ENREGISTRER;ETAT;RESULTAT;VARIANT_P;\ VARIANT_C;ENST;NM;POSITION_GENOMIQUE;CLASSESUR5;CLASSESUR3;COSMIC;RS;\ REFERENCES;CONSEQUENCES;COMMENTAIRE;CHROMOSOME;GENOME_REFERENCE;\ NOMENCLATURE_HGVS;LOCALISATION;SEQUENCE_REF;LOCUS;ALLELE1;ALLELE2\r\n" - rsid = '' - if vf['dbsnp_id']: - rsid = 'rs{0}'.format(vf['dbsnp_id']) - file_content += "{0};{1}:c.{2};;;;p.{3};c.{2};{4};{1};{5};\ + + "{0};{1}:c.{2};;;;p.{3};c.{2};{4};{1};{5};\ ;;;{6};;{7};;chr{8};{9};chr{8}:g.{10};{11} {12};;;;\r\n".format( - vf['gene_symbol'], vf['refseq'], - vf['c_name'], vf['p_name'], vf['enst'], vf['pos'], - rsid, vf['prot_type'], vf['chr'], genome, - vf['g_name'], vf['start_segment_type'], vf['start_segment_number'] + vf['gene_symbol'], + vf['refseq'], + vf['c_name'], + vf['p_name'], + vf['enst'], + vf['pos'], + rsid, + vf['prot_type'], + vf['chr'], + genome, + vf['g_name'], + vf['start_segment_type'], + vf['start_segment_number'], + ) ) # print(file_content) app_path = os.path.dirname(os.path.realpath(__file__)) @@ -309,12 +316,9 @@ def defgen(): md_utilities.send_error_email( md_utilities.prepare_email_html( 'MobiDetails Ajax error', - '

DefGen file generation failed in {} (no variant_id)

' - .format( - os.path.basename(__file__) - ) + f'

DefGen file generation failed in {os.path.basename(__file__)} (no variant_id)

', ), - '[MobiDetails - Ajax Error]' + '[MobiDetails - Ajax Error]', ) return """
@@ -343,12 +347,12 @@ def intervar(): match_ref and \ match_alt and \ match_gene_symbol: - genome = match_genome.group(1) - chrom = match_nochr_chrom.group(1) - pos = match_pos.group(1) - ref = match_ref.group(1) - alt = match_alt.group(1) - gene = match_gene_symbol.group(1) + genome = match_genome[1] + chrom = match_nochr_chrom[1] + pos = match_pos[1] + ref = match_ref[1] + alt = match_alt[1] + gene = match_gene_symbol[1] header = md_utilities.api_agent if len(ref) > 1 or len(alt) > 1: return 'No wintervar for indels' @@ -396,17 +400,12 @@ def intervar(): headers=header ).data.decode('utf-8') ) - i = 0 - for obj in intervar_list: + for i, obj in enumerate(intervar_list): # some housekeeping to get proper strings obj = obj.replace('{', '').replace('}', '') obj = '{{{}}}'.format(obj) intervar_list[i] = obj - i += 1 - intervar_data = [] - for obj in intervar_list: - # print(obj) - intervar_data.append(json.loads(obj)) + intervar_data = [json.loads(obj) for obj in intervar_list] except Exception as e: md_utilities.send_error_email( md_utilities.prepare_email_html( @@ -465,31 +464,30 @@ def intervar(): key, md_utilities.acmg_criteria[key] ) - if intervar_acmg is not None: - db = get_db() - curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) - curs.execute( - """ + if intervar_acmg is None: + return "No wintervar class" + db = get_db() + curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) + curs.execute( + """ SELECT html_code FROM valid_class WHERE acmg_translation = %s """, - (intervar_acmg.lower(),) - ) - res = curs.fetchone() - close_db() - return """ + (intervar_acmg.lower(),) + ) + res = curs.fetchone() + close_db() + return """ {1} with the following criteria:

{2}

""".format( - res['html_code'], - intervar_acmg, - intervar_criteria - ) - else: - return "No wintervar class" + res['html_code'], + intervar_acmg, + intervar_criteria + ) else: md_utilities.send_error_email( md_utilities.prepare_email_html( diff --git a/MobiDetailsApp/api.py b/MobiDetailsApp/api.py index 7f2c8a75..492a0892 100644 --- a/MobiDetailsApp/api.py +++ b/MobiDetailsApp/api.py @@ -48,8 +48,7 @@ def check_api_key(api_key=None): @bp.route('/api/service/vv_instance', methods=['GET']) def check_vv_instance(): - vv_url = md_utilities.get_vv_api_url() - if vv_url: + if vv_url := md_utilities.get_vv_api_url(): if vv_url == md_utilities.urls['variant_validator_api']: return jsonify( variant_validator_instance='Running genuine VV server', @@ -76,47 +75,57 @@ def api_variant_exists(variant_ghgvs=None): return jsonify(mobidetails_error='No variant submitted') variant_regexp = md_utilities.regexp['variant'] chrom_regexp = md_utilities.regexp['ncbi_chrom'] - match_variant_ghgvs = re.search(rf'^({chrom_regexp}):g\.({variant_regexp})$', urllib.parse.unquote(variant_ghgvs)) - # match_object = re.search(r'^([Nn][Cc]_0000\d{2}\.\d{1,2}):g\.(.+)$', urllib.parse.unquote(variant_ghgvs)) - if match_variant_ghgvs: - db = get_db() - chrom, genome_version = md_utilities.get_common_chr_name(db, match_variant_ghgvs.group(1)) - if chrom and \ + if not ( + match_variant_ghgvs := re.search( + rf'^({chrom_regexp}):g\.({variant_regexp})$', + urllib.parse.unquote(variant_ghgvs), + ) + ): + return jsonify(mobidetails_error='Malformed query, please check your input') + db = get_db() + chrom, genome_version = md_utilities.get_common_chr_name( + db, match_variant_ghgvs[1] + ) + if chrom and \ genome_version: - pattern = match_variant_ghgvs.group(2) - curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) - curs.execute( - """ + pattern = match_variant_ghgvs[2] + curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) + curs.execute( + """ SELECT feature_id FROM variant WHERE chr = %s AND g_name = %s AND genome_version = %s """, - (chrom, pattern, genome_version) - ) - res = curs.fetchone() - if res is not None: - close_db() - return jsonify( - mobidetails_id=res['feature_id'], - url='{0}{1}'.format( - request.host_url[:-1], - url_for( - 'api.variant', - variant_id=res['feature_id'], - caller='browser' - ) + (chrom, pattern, genome_version) + ) + res = curs.fetchone() + if res is not None: + close_db() + return jsonify( + mobidetails_id=res['feature_id'], + url='{0}{1}'.format( + request.host_url[:-1], + url_for( + 'api.variant', + variant_id=res['feature_id'], + caller='browser' ) ) - else: - close_db() - checked_var_ghgvs = '{0}:g.{1}'.format(match_variant_ghgvs.group(1), match_variant_ghgvs.group(2)) - return jsonify(mobidetails_warning='The variant {} does not exist yet in MD'.format(checked_var_ghgvs)) + ) else: - return jsonify(mobidetails_error='The chromosome {} does not exist in MD'.format(match_variant_ghgvs.group(1))) + close_db() + checked_var_ghgvs = '{0}:g.{1}'.format( + match_variant_ghgvs[1], match_variant_ghgvs[2] + ) + return jsonify( + mobidetails_warning=f'The variant {checked_var_ghgvs} does not exist yet in MD' + ) else: - return jsonify(mobidetails_error='Malformed query, please check your input') + return jsonify( + mobidetails_error=f'The chromosome {match_variant_ghgvs[1]} does not exist in MD' + ) # ------------------------------------------------------------------- # api - variant diff --git a/MobiDetailsApp/auth.py b/MobiDetailsApp/auth.py index 10a6b0ab..78f9d46a 100644 --- a/MobiDetailsApp/auth.py +++ b/MobiDetailsApp/auth.py @@ -36,21 +36,22 @@ def register(): error = username = password = country = institute = email = None if not request.form['username']: error = 'Username is required.' + elif match_obj := re.search( + r'^([\w-]{5,100})$', request.form['username'] + ): + username = match_obj[1] else: - match_obj = re.search(r'^([\w-]{5,100})$', request.form['username']) - if match_obj: - username = match_obj.group(1) - else: - error = """ + error = """ Username should be at least 5 characters and contain only letters and numbers. """ if not request.form['password']: error = 'Password is required.' elif not error: - # https://stackoverflow.com/questions/4429847/check-if-string-contains-both-number-and-letter-at-least - match_obj = re.search(r'(?!^[0-9]*$)(?!^[a-z]*$)(?!^[A-Z]*$)(?!^[a-zA-Z]*$)(?!^[a-z0-9]*$)(?!^[A-Z0-9]*$)^(.{8,})$', request.form['password']) - if match_obj: - password = match_obj.group(1) + if match_obj := re.search( + r'(?!^[0-9]*$)(?!^[a-z]*$)(?!^[A-Z]*$)(?!^[a-zA-Z]*$)(?!^[a-z0-9]*$)(?!^[A-Z0-9]*$)^(.{8,})$', + request.form['password'], + ): + password = match_obj[1] else: error = """ Password should be at least 8 characters and mix at least letters (upper and lower case) and numbers. @@ -68,20 +69,20 @@ def register(): if not request.form['institute']: error = 'Institute is required.' elif not error: - match_obj = re.search(r'^([\w\(\),;\.\s-]+)$', request.form['institute']) - if match_obj: - institute = match_obj.group(1) + if match_obj := re.search( + r'^([\w\(\),;\.\s-]+)$', request.form['institute'] + ): + institute = match_obj[1] else: error = 'Invalid characters in the Institute field (please use letters, numbers and ,;.-()).' if not request.form['email']: error = 'Email is required.' elif not error: - match_obj = re.search( + if match_obj := re.search( r'^([a-zA-Z0-9\._%\+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$', - request.form['email'] - ) - if match_obj: - email = match_obj.group(1) + request.form['email'], + ): + email = match_obj[1] else: error = 'The email address does not look valid.' academic_val = 't' if request.form['acad'] == 'academic' else 'f' @@ -89,12 +90,11 @@ def register(): if not request.remote_addr: error = 'Invalid request' elif not error: - match_obj = re.search(r'^([\d\.]+)$', request.remote_addr) - if match_obj: - remote_ip = match_obj.group(1) + if match_obj := re.search(r'^([\d\.]+)$', request.remote_addr): + remote_ip = match_obj[1] else: error = 'Invalid char in IP address.' - + # username = urllib.parse.unquote(request.form['username']) @@ -106,7 +106,7 @@ def register(): # academic = request.form['acad'] # header = md_utilities.api_agent # remote_ip = request.remote_addr - + db = get_db() curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) @@ -176,11 +176,12 @@ def register(): if mv_json: try: if mv_json['credits_available'] > 0: - if mv_json['status'] == "False": - if (mv_json['is_high_risk'] == "True" or - mv_json['is_suppressed'] == "True" or - mv_json['is_catchall'] == "True"): - error = """ + if mv_json['status'] == "False" and ( + mv_json['is_high_risk'] == "True" + or mv_json['is_suppressed'] == "True" + or mv_json['is_catchall'] == "True" + ): + error = """ The email address is reported as risky or suppressed. If this is not the case, please send us an email directly to @@ -190,8 +191,6 @@ def register(): gmail. com. """ - # else:valid adressese such as - # d-baux@chu-montpellier.fr are reported as False else: md_utilities.send_error_email( md_utilities.prepare_email_html( @@ -454,9 +453,9 @@ def login(): session.clear() close_db() flash( - 'You have successfully been logged in as {}.'.format( - user["username"]), 'w3-pale-green' - ) + f'You have successfully been logged in as {user["username"]}.', + 'w3-pale-green', + ) session['user_id'] = user['id'] if referrer_page is None or \ (url_parse(referrer_page).host != @@ -470,27 +469,26 @@ def login(): ), code=302 ) + if referrer_page is not None and \ + (url_parse(referrer_page).host == + url_parse(request.base_url).host): + # not coming from mobidetails + # rebuild redirect URL + return redirect( + md_utilities.build_redirect_url( + referrer_page + ), + code=302 + ) else: - if referrer_page is not None and \ - (url_parse(referrer_page).host == - url_parse(request.base_url).host): - # not coming from mobidetails - # rebuild redirect URL - return redirect( - md_utilities.build_redirect_url( - referrer_page - ), - code=302 - ) - else: - return redirect( - url_for( - 'auth.profile', - run_mode=md_utilities.get_running_mode(), - mobiuser_id=0 - ), - code=302 - ) + return redirect( + url_for( + 'auth.profile', + run_mode=md_utilities.get_running_mode(), + mobiuser_id=0 + ), + code=302 + ) close_db() flash(error, 'w3-pale-red') # not coming from mobidetails @@ -587,9 +585,7 @@ def wrapped_view(**kwargs): @login_required def profile(mobiuser_id=0): if re.search(r'^\d+$', str(mobiuser_id)): - user_id = g.user['id'] - if mobiuser_id != 0: - user_id = mobiuser_id + user_id = mobiuser_id if mobiuser_id != 0 else g.user['id'] db = get_db() curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) curs.execute( @@ -680,11 +676,11 @@ def profile(mobiuser_id=0): """, (g.user['id'],) ) - num_var_clinv = curs.rowcount - if error is None: # num_var_fav = curs.rowcount close_db() + num_var_clinv = curs.rowcount + return render_template( 'auth/profile.html', run_mode=md_utilities.get_running_mode(), @@ -718,16 +714,13 @@ def profile(mobiuser_id=0): flash(error, 'w3-pale-red') close_db() - return render_template( - 'md/index.html', - run_mode=md_utilities.get_running_mode() - ) else: ('Invalid user ID!!', 'w3-pale-red') - return render_template( - 'md/index.html', - run_mode=md_utilities.get_running_mode() - ) + + return render_template( + 'md/index.html', + run_mode=md_utilities.get_running_mode() + ) # ------------------------------------------------------------------- # load profile when browsing @@ -757,17 +750,13 @@ def load_logged_in_user(): def logout(): session.clear() flash('You have successfully been logged out.', 'w3-pale-green') - # https://pythonise.com/series/learning-flask/the-flask-request-object - # url attributes - # scheme auth username password raw_username raw_password - # ascii_host host port path query fragment - if request.referrer is not None and \ - (url_parse(request.referrer).host == md_utilities.host['dev'] or - url_parse(request.referrer).host == md_utilities.host['prod']): - redirect_url = md_utilities.build_redirect_url(request.referrer) - return redirect(redirect_url, code=302) - else: + if request.referrer is None or url_parse(request.referrer).host not in [ + md_utilities.host['dev'], + md_utilities.host['prod'], + ]: return redirect(url_for('index'), code=302) + redirect_url = md_utilities.build_redirect_url(request.referrer) + return redirect(redirect_url, code=302) # ------------------------------------------------------------------- # forgot password @@ -785,14 +774,10 @@ def forgot_pass(): elif request.method == 'POST': error = None email = request.form['email'] - if not re.search( + if re.search( r'^[a-zA-Z0-9\._%\+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email ): - error = 'The email address does not look valid.' - flash(error, 'w3-pale-red') - return render_template('auth/forgot_pass.html') - else: db = get_db() curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) curs.execute( @@ -843,7 +828,9 @@ def forgot_pass(): , 'w3-pale-green' ) close_db() - return render_template('auth/forgot_pass.html') + else: + flash('The email address does not look valid.', 'w3-pale-red') + return render_template('auth/forgot_pass.html') return render_template('md/unknown.html') # ------------------------------------------------------------------- @@ -980,39 +967,35 @@ def reset_password(): @bp.route('/variant_list/', methods=['GET']) def variant_list(list_name): - if len(list_name) < 31 and \ - re.search(r'^[\w]+$', list_name): - db = get_db() - curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) - curs.execute( - """ + if len(list_name) >= 31 or not re.search(r'^[\w]+$', list_name): + return render_template('errors/404.html'), 404 + db = get_db() + curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) + curs.execute( + """ SELECT a.variant_ids, b.username, a.creation_date, a.list_name FROM variants_groups a, mobiuser b WHERE a.mobiuser_id = b.id AND list_name = %s """, - (list_name,) - ) - res = curs.fetchone() - # we've got an ARRAY of variants_ids and want to query on them... - if res: - res_ids_string = "(" - for id in res['variant_ids']: - res_ids_string += "{}, ".format(id) - res_ids_string = res_ids_string[:-2] - res_ids_string += ")" - # print(res_ids_string) - curs.execute( - """ + (list_name,) + ) + if res := curs.fetchone(): + res_ids_string = "(" + for id in res['variant_ids']: + res_ids_string += f"{id}, " + res_ids_string = res_ids_string[:-2] + res_ids_string += ")" + # print(res_ids_string) + curs.execute( + """ SELECT a.id, a.c_name, a.p_name, a.gene_symbol, a.refseq, a.creation_user, a.creation_date, b.username FROM variant_feature a, mobiuser b WHERE a.creation_user = b.id AND a.id IN {0} """.format(res_ids_string) - ) - variants = curs.fetchall() - close_db() - return render_template('md/variant_multiple.html', variants=variants, unique_url_info=res) - else: - close_db() - return render_template('errors/404.html'), 404 + ) + variants = curs.fetchall() + close_db() + return render_template('md/variant_multiple.html', variants=variants, unique_url_info=res) else: + close_db() return render_template('errors/404.html'), 404 diff --git a/MobiDetailsApp/db.py b/MobiDetailsApp/db.py index b104bd74..daec83e4 100644 --- a/MobiDetailsApp/db.py +++ b/MobiDetailsApp/db.py @@ -45,7 +45,7 @@ def close_db(e=None): def init_db(): db = get_db() curs = db.cursor() - with open(os.getcwd() + "/MobiDetailsApp/sql/MobiDetails.sql", "r") as sql_file: + with open(f"{os.getcwd()}/MobiDetailsApp/sql/MobiDetails.sql", "r") as sql_file: curs.execute(sql_file.read()) diff --git a/MobiDetailsApp/md_utilities.py b/MobiDetailsApp/md_utilities.py index 0764bad5..1f5137ef 100644 --- a/MobiDetailsApp/md_utilities.py +++ b/MobiDetailsApp/md_utilities.py @@ -23,7 +23,7 @@ app_path = os.path.dirname(os.path.realpath(__file__)) # get config file with paths, etc -with open('{}/sql/md_resources.yaml'.format(app_path), "r") as resources_file: +with open(f'{app_path}/sql/md_resources.yaml', "r") as resources_file: resources = yaml.safe_load(resources_file) # resources = yaml.safe_load(open('{}/sql/md_resources.yaml'.format(app_path))) @@ -47,14 +47,10 @@ def get_resource_current_version(resource_dir, regexp, excluded_date=None): files = os.listdir(resource_dir) dates = [] for current_file in files: - # print(current_file) - # match_obj = re.search(rf'clinvar_(\d+).vcf.gz$', current_file) - match_obj = re.search(rf'{regexp}$', current_file) - if match_obj: - if excluded_date and \ - excluded_date == match_obj.group(1): + if match_obj := re.search(rf'{regexp}$', current_file): + if excluded_date and excluded_date == match_obj[1]: continue - dates.append(match_obj.group(1)) + dates.append(match_obj[1]) return max(dates) @@ -187,15 +183,11 @@ def get_resource_current_version(resource_dir, regexp, excluded_date=None): external_tools[tool]['paper'] = '{0}{1}'.format( urls['ncbi_pubmed'], external_tools[tool]['paper'] ) -external_tools['ClinVar']['version'] = 'v{}'.format( - clinvar_version -) -external_tools['ClinGenSpecificationRegistry']['version'] = 'v{}'.format( - clingen_version -) -external_tools['OncoKBGenes']['version'] = 'v{}'.format( - oncokb_genes_version -) +external_tools['ClinVar']['version'] = f'v{clinvar_version}' +external_tools['ClinGenSpecificationRegistry'][ + 'version' +] = f'v{clingen_version}' +external_tools['OncoKBGenes']['version'] = f'v{oncokb_genes_version}' acmg_criteria = resources['acmg'] lovd_effect = resources['lovd_effect'] spip_headers = resources['spip_headers'] @@ -230,60 +222,61 @@ def clean_var_name(variant): if re.search('>', variant): variant = variant.upper() elif re.search('d[eu][lp]', variant): - match_obj = re.search(r'^(.+d[eu][lp])[ATCG]+$', variant) - if match_obj: - variant = match_obj.group(1) - else: - match_obj = re.search(r'^(.+del)[ATCG]+(ins[ACTG])$', variant) - if match_obj: - variant = match_obj.group(1) + match_obj.group(2) + if match_obj := re.search(r'^(.+d[eu][lp])[ATCG]+$', variant): + variant = match_obj[1] + elif match_obj := re.search(r'^(.+del)[ATCG]+(ins[ACTG])$', variant): + variant = match_obj[1] + match_obj[2] return variant def three2one_fct(var): var = clean_var_name(var) - match_object = re.search(r'^(\w{3})(\d+)(\w{3}|[X\*=])$', var) - if match_object: - if re.search('d[ue][pl]', match_object.group(3)): - return three2one[match_object.group(1).capitalize()] + \ - match_object.group(2) + \ - match_object.group(3) + if match_object := re.search(r'^(\w{3})(\d+)(\w{3}|[X\*=])$', var): + if re.search('d[ue][pl]', match_object[3]): + return ( + three2one[match_object[1].capitalize()] + + match_object[2] + + match_object[3] + ) else: - return three2one[match_object.group(1).capitalize()] + \ - match_object.group(2) + \ - three2one[match_object.group(3).capitalize()] - match_object = re.search(r'^(\w{3})(\d+_)(\w{3})(\d+.+)$', var) - if match_object: - return three2one[match_object.group(1)].capitalize() + \ - match_object.group(2) + \ - three2one[match_object.group(3)].capitalize() + \ - match_object.group(4) - match_object = re.search(r'^(\w{3})(\d+)\w{3}fsTer\d+$', var) - if match_object: - return three2one[match_object.group(1)].capitalize() + \ - match_object.group(2) + \ - 'fs' + return ( + three2one[match_object[1].capitalize()] + + match_object[2] + + three2one[match_object[3].capitalize()] + ) + if match_object := re.search(r'^(\w{3})(\d+_)(\w{3})(\d+.+)$', var): + return ( + three2one[match_object[1]].capitalize() + + match_object[2] + + three2one[match_object[3]].capitalize() + + match_object[4] + ) + if match_object := re.search(r'^(\w{3})(\d+)\w{3}fsTer\d+$', var): + return three2one[match_object[1]].capitalize() + match_object[2] + 'fs' return None def one2three_fct(var): var = clean_var_name(var) - match_object = re.search(r'^(\w{1})(\d+)([\w\*=]{1})$', var) - if match_object: - return one2three[match_object.group(1).capitalize()] + \ - match_object.group(2) + \ - one2three[match_object.group(3).capitalize()] - match_object = re.search(r'^(\w{1})(\d+)(d[ue][pl])$', var) - if match_object: - return one2three[match_object.group(1).capitalize()] + \ - match_object.group(2) + \ - match_object.group(3) - match_object = re.search(r'^(\w{1})(\d+_)(\w{1})(\d+.+)$', var) - if match_object: - return one2three[match_object.group(1).capitalize()] + \ - match_object.group(2) + \ - one2three[match_object.group(3).capitalize()] + \ - match_object.group(4) + if match_object := re.search(r'^(\w{1})(\d+)([\w\*=]{1})$', var): + return ( + one2three[match_object[1].capitalize()] + + match_object[2] + + one2three[match_object[3].capitalize()] + ) + if match_object := re.search(r'^(\w{1})(\d+)(d[ue][pl])$', var): + return ( + one2three[match_object[1].capitalize()] + + match_object[2] + + match_object[3] + ) + if match_object := re.search(r'^(\w{1})(\d+_)(\w{1})(\d+.+)$', var): + return ( + one2three[match_object[1].capitalize()] + + match_object[2] + + one2three[match_object[3].capitalize()] + + match_object[4] + ) return None @@ -291,8 +284,7 @@ def get_ncbi_chr_name(db, chr_name, genome): # get NCBI chr names for common names curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) if is_valid_full_chr(chr_name): - short_chr = get_short_chr_name(chr_name) - if short_chr: + if short_chr := get_short_chr_name(chr_name): curs.execute( """ SELECT ncbi_name @@ -302,9 +294,7 @@ def get_ncbi_chr_name(db, chr_name, genome): """, (genome, short_chr) ) - ncbi_name = curs.fetchone() - # print(ncbi_name) - if ncbi_name: + if ncbi_name := curs.fetchone(): return ncbi_name return None @@ -321,17 +311,14 @@ def get_common_chr_name(db, ncbi_name): """, (ncbi_name,) ) - res = curs.fetchone() - if res: + if res := curs.fetchone(): return res return None, None def is_valid_full_chr(chr_name): # chr name is valid? nochr_captured_regexp = regexp['nochr_captured'] - if re.search(rf'^[Cc][Hh][Rr]({nochr_captured_regexp})$', chr_name): - return True - return False + return bool(re.search(rf'^[Cc][Hh][Rr]({nochr_captured_regexp})$', chr_name)) def get_short_chr_name(chr_name): # get small chr name @@ -340,20 +327,16 @@ def get_short_chr_name(chr_name): # get small chr name rf'^[Cc][Hh][Rr]({nochr_captured_regexp})$', chr_name ) if match_obj is not None: - return match_obj.group(1) + return match_obj[1] def is_valid_chr(chr_name): # chr name is valid? nochr_captured_regexp = regexp['nochr_captured'] - if re.search(rf'^({nochr_captured_regexp})$', chr_name): - return True - return False + return bool(re.search(rf'^({nochr_captured_regexp})$', chr_name)) def is_valid_ncbi_chr(chr_name): # NCBI chr name is valid? - if re.search(r'^[Nn][Cc]_0000\d{2}\.\d{1,2}$', chr_name): - return True - return False + return bool(re.search(r'^[Nn][Cc]_0000\d{2}\.\d{1,2}$', chr_name)) def translate_genome_version(genome_version): @@ -369,10 +352,13 @@ def translate_genome_version(genome_version): def decompose_vcf_str(vcf_str): # chr_regexp = regexp['nochr_captured'] vcf_str_regexp = regexp['vcf_str_captured'] - match_obj = re.search(rf'^{vcf_str_regexp}$', vcf_str) - # match_obj = re.search(rf'[Cc]?[Hh]?[Rr]?({chr_regexp})[:-](\d+)[:-]([ACTGactg]+)[:-]([ACTGactg]+)', vcf_str) - if match_obj: - return match_obj.group(1), int(match_obj.group(2)), match_obj.group(3).upper(), match_obj.group(4).upper() + if match_obj := re.search(rf'^{vcf_str_regexp}$', vcf_str): + return ( + match_obj[1], + int(match_obj[2]), + match_obj[3].upper(), + match_obj[4].upper(), + ) return None, None, None, None @@ -389,14 +375,9 @@ def get_var_genic_csq(var): def is_higher_genic_csq(new_csq, old_csq): # function to assess exon>intron>5UTR (designed originally for vcf_str api endpoint but not used) - if new_csq == 'exon' or \ - new_csq == old_csq: + if new_csq in ['exon', old_csq]: return True - elif new_csq != 'exon' and \ - old_csq == 'exon': - return False - elif new_csq == '5UTR' and \ - old_csq == 'intron': + elif old_csq == 'exon' or new_csq == '5UTR' and old_csq == 'intron': return False return True @@ -423,24 +404,12 @@ def get_pos_splice_site(pos, positions): def get_pos_splice_site_intron(name): - # get position of intronic variant to the nearest ss - match_obj = re.search(r'^[\*-]?\d+([\+-])(\d+)[^\d_]', name) - if match_obj: - return [ - int(match_obj.group(2)), - match_obj.group(1) - ] - match_obj = re.search( + if match_obj := re.search(r'^[\*-]?\d+([\+-])(\d+)[^\d_]', name): + return [int(match_obj[2]), match_obj[1]] + if match_obj := re.search( r'[\*-]?\d+([\+-])(\d+)_[\*-]?\d+[\+-](\d+)[^\d_]', name - ) - if match_obj: - return [ - min( - int(match_obj.group(2)), - int(match_obj.group(3)) - ), - match_obj.group(1) - ] + ): + return [min(int(match_obj[2]), int(match_obj[3])), match_obj[1]] if re.search(r'[\*-]?\d+[-]\d+_[\*-]?\d+[^\d_]', name): # overlapping variant e.g. -30-12_-8dup return [1, '-'] @@ -496,39 +465,37 @@ def get_exon_neighbours(db, positions): def get_exon_sequence(positions, chrom, strand): # get DNA sequence for a given exon - if isinstance(positions['number'], int) and \ - re.search(r'^NM_\d+\.\d+$', positions['refseq']) and \ - re.search(r'^\d+$', chrom) and \ - re.search(r'^[\+-]+', strand): - genome = twobitreader.TwoBitFile( - '{}.2bit'.format(local_files['human_genome_hg38']['abs_path']) - ) - current_chrom = genome['chr{}'.format(chrom)] - exon_start = int(positions['segment_start']) - exon_end = int(positions['segment_end']) - if exon_start < exon_end: - exon_seq = current_chrom[exon_start-1:exon_end].upper() - else: - exon_seq = current_chrom[exon_end-1:exon_start].upper() - if strand == '-': - exon_seq = reverse_complement(exon_seq).upper() - return exon_seq - return 'Wrong or lacking parameter' + if ( + not isinstance(positions['number'], int) + or not re.search(r'^NM_\d+\.\d+$', positions['refseq']) + or not re.search(r'^\d+$', chrom) + or not re.search(r'^[\+-]+', strand) + ): + return 'Wrong or lacking parameter' + genome = twobitreader.TwoBitFile( + f"{local_files['human_genome_hg38']['abs_path']}.2bit" + ) + current_chrom = genome[f'chr{chrom}'] + exon_start = int(positions['segment_start']) + exon_end = int(positions['segment_end']) + if exon_start < exon_end: + exon_seq = current_chrom[exon_start-1:exon_end].upper() + else: + exon_seq = current_chrom[exon_end-1:exon_start].upper() + if strand == '-': + exon_seq = reverse_complement(exon_seq).upper() + return exon_seq def get_exonic_substitution_position(var_cdna): - # from 158C>T get 158 - match_obj = re.search(r'^[\*-]?(\d+)[ATCG]', var_cdna) - if match_obj: - return match_obj.group(1) + if match_obj := re.search(r'^[\*-]?(\d+)[ATCG]', var_cdna): + return match_obj[1] return None def get_substitution_nature(var_cdna): - # from 158C>T or 158-1C>T get C>T - match_obj = re.search(r'([ATCG]>[ATGC])$', var_cdna) - if match_obj: - return match_obj.group(1) + if match_obj := re.search(r'([ATCG]>[ATGC])$', var_cdna): + return match_obj[1] return None @@ -545,13 +512,11 @@ def get_exon_first_nt_cdna_position(positions, var_gpos, var_c): def get_aa_position(hgvs_p): # get aa position fomr hgvs p. (3 letter) - match_object = re.search(r'^\w{3}(\d+)_\w{3}(\d+)[^\d]+$', hgvs_p) - if match_object: + if match_object := re.search(r'^\w{3}(\d+)_\w{3}(\d+)[^\d]+$', hgvs_p): # return "{0}_{1}".format(match_object.group(1), match_object.group(2)) - return match_object.group(1), match_object.group(2) - match_object = re.search(r'^\w{3}(\d+)[^\d]+.*$', hgvs_p) - if match_object: - return match_object.group(1), match_object.group(1) + return match_object[1], match_object[2] + if match_object := re.search(r'^\w{3}(\d+)[^\d]+.*$', hgvs_p): + return match_object[1], match_object[1] return None, None @@ -566,8 +531,7 @@ def get_user_id(username, db): """, (username,) ) - res_user = curs.fetchone() - if res_user: + if res_user := curs.fetchone(): return res_user['id'] return None @@ -576,9 +540,9 @@ def define_lovd_class(acmg_classes, db): lovd_class = None if isinstance(acmg_classes, list): for acmg_class in acmg_classes: - if (isinstance(acmg_class, list) or - isinstance(acmg_class, dict)) and \ - 'acmg_class' in acmg_class: + if ( + isinstance(acmg_class, (list, dict)) + ) and 'acmg_class' in acmg_class: # list => true DictRow from psycopg2 # dict for pytest # print(acmg_classes) @@ -592,15 +556,15 @@ def define_lovd_class(acmg_classes, db): break elif lovd_class != 'Conflicting': # do sthg - if (acmg_class['acmg_class'] == 1 or - acmg_class['acmg_class'] == 2) and \ - (lovd_class == 'Likely Pathogenic' or - lovd_class == 'Pathogenic'): + if acmg_class['acmg_class'] in [1, 2] and lovd_class in [ + 'Likely Pathogenic', + 'Pathogenic', + ]: lovd_class = 'Conflicting' - elif (acmg_class['acmg_class'] == 4 or - acmg_class['acmg_class'] == 5) and \ - (lovd_class == 'Likely benign' or - lovd_class == 'Benign'): + elif acmg_class['acmg_class'] in [4, 5] and lovd_class in [ + 'Likely benign', + 'Benign', + ]: lovd_class = 'Conflicting' elif (lovd_class == 'Benign' and acmg_class['acmg_class'] == 2): @@ -611,10 +575,10 @@ def define_lovd_class(acmg_classes, db): elif (lovd_class == 'Pathogenic' and acmg_class['acmg_class'] == 4): lovd_class = 'Likely Pathogenic' - elif (lovd_class == 'Likely Pathogenic' and - acmg_class['acmg_class'] == 5): - pass - else: + elif ( + lovd_class != 'Likely Pathogenic' + or acmg_class['acmg_class'] != 5 + ): lovd_class = acmg2lovd(acmg_class['acmg_class'], db) # print('current acmg:{0} - current lovd: {1}'.format( # acmg_class['acmg_class'], lovd_class)) @@ -632,8 +596,7 @@ def acmg2lovd(acmg_class, db): """, (acmg_class,) ) - res_lovd_acmg = curs.fetchone() - if res_lovd_acmg: + if res_lovd_acmg := curs.fetchone(): return res_lovd_acmg[0] return None @@ -661,7 +624,7 @@ def get_value_from_tabix_file(text, tabix_file, var, variant_features): ), '[MobiDetails - Tabix Error]' ) - return 'Match failed in {}'.format(text) + return f'Match failed in {text}' i = 3 if re.search( r'(dbNSFP|whole_genome_SNVs|dbscSNV|dbMTS|revel|MISTIC|CADD/hg38/v1\.6/gnomad.genomes\.r3\.0\.indel)', @@ -708,13 +671,14 @@ def get_value_from_tabix_file(text, tabix_file, var, variant_features): aa2 == record[j+1] and \ ppos in ppos_list: return record - return 'No match in {}'.format(text) + return f'No match in {text}' def decompose_missense(p_name): - match_obj = re.search(r'^([A-Z][a-z]{2})(\d+)([A-Z][a-z]{2})$', p_name) - if match_obj: - return three2one[match_obj.group(1)], match_obj.group(2), three2one[match_obj.group(3)] + if match_obj := re.search( + r'^([A-Z][a-z]{2})(\d+)([A-Z][a-z]{2})$', p_name + ): + return three2one[match_obj[1]], match_obj[2], three2one[match_obj[3]] return None, None, None @@ -726,15 +690,10 @@ def getdbNSFP_results( pred = 'no prediction' star = '' try: - score = re.split( - '{}'.format(sep), dbnsfp_record[score_index] - )[transcript_index] + score = re.split(f'{sep}', dbnsfp_record[score_index])[transcript_index] if pred_index != score_index: pred = predictors_translations[translation_mode][ - re.split( - '{}'.format(sep), - dbnsfp_record[pred_index] - )[transcript_index] + re.split(f'{sep}', dbnsfp_record[pred_index])[transcript_index] ] if score == '.': # search most deleterious in other isoforms score, pred, star = get_most_other_deleterious_pred( @@ -774,23 +733,18 @@ def get_spliceai_color(val): def get_most_other_deleterious_pred( score, pred, threshold, direction, pred_type): - # returns most deleterious score of predictors - # when not found in desired transcript - j = 0 k = 0 best_score = threshold - for score in re.split(';', score): + for j, score in enumerate(re.split(';', score)): if direction == 'lt': if score != '.' and \ float(score) < float(best_score): best_score = score k = j - else: - if score != '.' and \ + elif score != '.' and \ float(score) > float(best_score): - best_score = score - k = j - j += 1 + best_score = score + k = j if best_score != threshold: return best_score, predictors_translations[pred_type][re.split(';', pred)[k]], '*' return '.', 'no prediction', '' @@ -800,9 +754,7 @@ def get_preditor_single_threshold_color(val, predictor): # returns an html color depending on a single threshold # function to get green or red if val != '.': - value = float(val) - if predictor == 'sift': - value = 1-(float(val)) + value = 1-(float(val)) if predictor == 'sift' else float(val) if value > predictor_thresholds[predictor]: return predictor_colors['max'] return predictor_colors['min'] @@ -868,17 +820,13 @@ def compute_pos_end(g_name): # receives g_name as 216420460C>A or 76885812_76885817del match_object = re.search(r'^(\d+)[ATGC]>', g_name) if match_object is not None: - return match_object.group(1) - else: - match_object = re.search(r'_(\d+)[di]', g_name) - if match_object is not None: - return match_object.group(1) - else: - # case of single nt dels ins dup - match_object = re.search(r'^(\d+)[d]', g_name) - if match_object is not None: - return match_object.group(1) - return None + return match_object[1] + match_object = re.search(r'_(\d+)[di]', g_name) + if match_object is not None: + return match_object[1] + # case of single nt dels ins dup + match_object = re.search(r'^(\d+)[d]', g_name) + return match_object[1] if match_object is not None else None def compute_start_end_pos(name): @@ -886,27 +834,20 @@ def compute_start_end_pos(name): # from VCF and HGVS - for variants > 1bp match_object = re.search(r'(\d+)_(\d+)[di]', name) if match_object is not None: - return match_object.group(1), match_object.group(2) - else: - match_object = re.search(r'^(\d+)[ATGC]>', name) - if match_object is not None: - return match_object.group(1), match_object.group(1) - else: - # single nt del or delins - match_object = re.search(r'^(\d+)[d]', name) - if match_object is not None: - return match_object.group(1), match_object.group(1) - else: + return match_object[1], match_object[2] + match_object = re.search(r'^(\d+)[ATGC]>', name) + if match_object is not None: + return match_object[1], match_object[1] + # single nt del or delins + match_object = re.search(r'^(\d+)[d]', name) + if match_object is not None: + return match_object[1], match_object[1] # case where NM wt disagree with genomic wt - if re.search(r'^\d+=', name): - return '-1', '-1' - return None, None + return ('-1', '-1') if re.search(r'^\d+=', name) else (None, None) def danger_panel(var, warning): # to be used in create_var_vv - begin_txt = 'VariantValidator error: ' - if var == '': - begin_txt = '' + begin_txt = '' if var == '' else 'VariantValidator error: ' return """
X @@ -916,11 +857,7 @@ def danger_panel(var, warning): # to be used in create_var_vv def info_panel(text, var='', id_var='', color_class='w3-sand'): - # to print general info do not send var neither id_var - # Newly created variant: - c = 'c.' - if re.search(r'N[MR]_', var): - c = '' + c = '' if re.search(r'N[MR]_', var) else 'c.' link = '' if var != '': link = """ @@ -964,30 +901,23 @@ def get_vv_api_url(): urls['variant_validator_api_hello_backup'], urls['variant_validator_api_backup'] ) - if checked_url: - return checked_url - else: + if not checked_url: checked_url = test_vv_api_url( urls['variant_validator_api_hello'], urls['variant_validator_api'] ) - return checked_url + return checked_url else: - # the other way around - checked_url = test_vv_api_url( - urls['variant_validator_api_hello'], - urls['variant_validator_api'] - ) - if checked_url: + if checked_url := test_vv_api_url( + urls['variant_validator_api_hello'], urls['variant_validator_api'] + ): + return checked_url + if checked_url := test_vv_api_url( + urls['variant_validator_api_hello_backup'], + urls['variant_validator_api_backup'], + ): return checked_url else: - checked_url = test_vv_api_url( - urls['variant_validator_api_hello_backup'], - urls['variant_validator_api_backup'] - ) - if checked_url: - return checked_url - else: send_error_email( prepare_email_html( 'MobiDetails VariantValidator error', diff --git a/MobiDetailsApp/upload.py b/MobiDetailsApp/upload.py index 846151d5..e9246b06 100644 --- a/MobiDetailsApp/upload.py +++ b/MobiDetailsApp/upload.py @@ -41,149 +41,116 @@ def allowed_file(filename): @bp.route('/file_upload', methods=['GET', 'POST']) def file_upload(): - if request.method == 'POST': - if request.files: - uploaded_file = request.files['file'] - if uploaded_file.filename == "": - flash('No filename.', 'w3-pale-red') + if request.method == 'POST' and request.files: + uploaded_file = request.files['file'] + if uploaded_file.filename == "": + flash('No filename.', 'w3-pale-red') + return render_template('upload/upload_form.html') + # return redirect(request.url) + if allowed_file(uploaded_file.filename): + # filename = secure_filename(uploaded_file.filename) + lines = uploaded_file.read().decode().replace('\r\n', '\n').replace('\r', '\n').split('\n') + # print(lines) + # ok we've got the file + # get user API key or use MD + db = get_db() + curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) + http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where()) + # headers + header = md_utilities.api_agent + result = [] + api_key = md_utilities.get_api_key(g, curs) + if api_key is None: + flash('There is an issue in obtaining an API key') + close_db() return render_template('upload/upload_form.html') # return redirect(request.url) - if allowed_file(uploaded_file.filename): - # filename = secure_filename(uploaded_file.filename) - lines = uploaded_file.read().decode().replace('\r\n', '\n').replace('\r', '\n').split('\n') - # print(lines) - # ok we've got the file - # get user API key or use MD - db = get_db() - curs = db.cursor(cursor_factory=psycopg2.extras.DictCursor) - http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where()) - # headers - header = md_utilities.api_agent - result = [] - api_key = md_utilities.get_api_key(g, curs) - if api_key is None: - flash('There is an issue in obtaining an API key') - close_db() - return render_template('upload/upload_form.html') - # return redirect(request.url) # we need to check the format and send a proper query to the API - for line in lines: - # print('-{}-'.format(line)) - # cDNA format - md_response = [] - if re.search(r'^#', line) or \ - line == '': - continue - line = line.replace(' ', '') - variant_regexp = md_utilities.regexp['variant'] - ncbi_transcript_regexp = md_utilities.regexp['ncbi_transcript'] - match_obj_c = re.search(rf'^({ncbi_transcript_regexp})\(*[\w-]*\)*:(c\.{variant_regexp})$', line) - if match_obj_c: + for line in lines: + # print('-{}-'.format(line)) + # cDNA format + md_response = [] + if re.search(r'^#', line) or \ + line == '': + continue + line = line.replace(' ', '') + variant_regexp = md_utilities.regexp['variant'] + ncbi_transcript_regexp = md_utilities.regexp['ncbi_transcript'] + if match_obj_c := re.search( + rf'^({ncbi_transcript_regexp})\(*[\w-]*\)*:(c\.{variant_regexp})$', + line, + ): # check NM number and version - curs.execute( - """ + curs.execute( + """ SELECT gene_symbol FROM gene WHERE refseq = %s """, - (match_obj_c.group(1),) - ) - res_gene = curs.fetchone() - if res_gene: - # send var to api - md_api_url = '{0}{1}'.format(request.host_url[:-1], url_for('api.api_variant_create')) + (match_obj_c[1],), + ) + if res_gene := curs.fetchone(): + # send var to api + md_api_url = '{0}{1}'.format(request.host_url[:-1], url_for('api.api_variant_create')) # md_api_url = '{0}/api/variant/create'.format(md_api_base_url) # print(md_api_url) - data = { - 'variant_chgvs': urllib.parse.quote( - '{0}:{1}'.format( - match_obj_c.group(1), - match_obj_c.group(2) - ) - ), - 'caller': 'cli', - 'api_key': api_key - } - try: - md_response = json.loads( - http.request( - 'POST', - md_api_url, - headers=header, - fields=data - ).data.decode('utf-8') - ) - result.append( - {'variant': line, - 'id': md_response['mobidetails_id'], - 'url': md_response['url']} + data = { + 'variant_chgvs': urllib.parse.quote( + '{0}:{1}'.format( + match_obj_c[1], match_obj_c[2] ) - except Exception: - if 'mobidetails_error' in md_response: - result.append({'variant': line, 'error': md_response['mobidetails_error']}) - else: - result.append({'variant': line, 'error': 'MDAPI call failed'}) - else: - result.append({'variant': line, 'error': 'Unknown NCBI NM accession number'}) - continue - # genomic format - ncbi_chrom_regexp = md_utilities.regexp['ncbi_chrom'] - match_obj_g = re.search(rf'^({ncbi_chrom_regexp}:g\.{variant_regexp});([\w-]+)$', line) - if match_obj_g: + ), + 'caller': 'cli', + 'api_key': api_key, + } + try: + md_response = json.loads( + http.request( + 'POST', + md_api_url, + headers=header, + fields=data + ).data.decode('utf-8') + ) + result.append( + {'variant': line, + 'id': md_response['mobidetails_id'], + 'url': md_response['url']} + ) + except Exception: + if 'mobidetails_error' in md_response: + result.append({'variant': line, 'error': md_response['mobidetails_error']}) + else: + result.append({'variant': line, 'error': 'MDAPI call failed'}) + else: + result.append({'variant': line, 'error': 'Unknown NCBI NM accession number'}) + continue + # genomic format + ncbi_chrom_regexp = md_utilities.regexp['ncbi_chrom'] + if match_obj_g := re.search( + rf'^({ncbi_chrom_regexp}:g\.{variant_regexp});([\w-]+)$', + line, + ): # check gene is available - curs.execute( - """ + curs.execute( + """ SELECT gene_symbol FROM gene WHERE gene_symbol = %s """, - (match_obj_g.group(2),) - ) - res_gene = curs.fetchone() - if res_gene: - # send var to api - md_api_url = '{0}{1}'.format(request.host_url[:-1], url_for('api.api_variant_g_create')) + (match_obj_g[2],), + ) + if res_gene := curs.fetchone(): + # send var to api + md_api_url = '{0}{1}'.format(request.host_url[:-1], url_for('api.api_variant_g_create')) # print(md_api_url) - data = { - 'variant_ghgvs': urllib.parse.quote(match_obj_g.group(1)), - 'gene_hgnc': match_obj_g.group(2), - 'caller': 'cli', - 'api_key': api_key - } - try: - md_response = json.loads( - http.request( - 'POST', - md_api_url, - headers=header, - fields=data - ).data.decode('utf-8') - ) - # print(md_response) - result.append({'variant': line, 'id': md_response['mobidetails_id']}) - except Exception: - if 'variant_validator_output' in md_response and \ - 'validation_warning_1' in md_response['variant_validator_output'] and \ - 'validation_warnings' in md_response['variant_validator_output']['validation_warning_1']: - result.append( - {'variant': line, - 'error': md_response['variant_validator_output']['validation_warning_1']['validation_warnings'][0]} - ) - elif 'mobidetails_error' in md_response: - result.append({'variant': line, 'error': md_response['mobidetails_error']}) - else: - result.append({'variant': line, 'error': 'MDAPI call failed'}) - else: - result.append({'variant': line, 'error': 'Unknown gene'}) - continue - if re.search(r'^rs\d+$', line): - md_api_url = '{0}{1}'.format(request.host_url[:-1], url_for('api.api_variant_create_rs')) - # md_api_url = '{0}/api/variant/create'.format(md_api_base_url) - # print(md_api_url) data = { - 'rs_id': line, + 'variant_ghgvs': urllib.parse.quote( + match_obj_g[1] + ), + 'gene_hgnc': match_obj_g[2], 'caller': 'cli', - 'api_key': api_key + 'api_key': api_key, } try: md_response = json.loads( @@ -194,73 +161,108 @@ def file_upload(): fields=data ).data.decode('utf-8') ) - for var in md_response: - # print(md_response[var]) - if 'mobidetails_error' in md_response[var]: - result.append( - {'variant': '{0} - {1}'.format(line, var), - 'error': md_response[var]['mobidetails_error']} - ) - elif var == 'mobidetails_error': - result.append({'variant': line, 'error': md_response[var]}) - else: - result.append( - {'variant': '{0} - {1}'.format(line, var), - 'id': md_response[var]['mobidetails_id'], - 'url': md_response[var]['url']} - ) - + # print(md_response) + result.append({'variant': line, 'id': md_response['mobidetails_id']}) except Exception: - result.append({'variant': line, 'error': 'MDAPI call failed'}) - continue - result.append({'variant': line, 'error': 'Bad format'}) - close_db() - flash('File correctly uploaded', 'w3-pale-green') - if g.user: - # send an email - # print(g.user) - result_list = '' - for resul in result: - result_list = '{0}
  • {1}'.format(result_list, resul['variant']) - if 'error' in resul: - result_list = '{0} - {1}
  • '.format(result_list, resul['error']) - else: - result_list = '{0} - success'.format( - result_list, - request.host_url[:-1], - url_for( - 'api.variant', - variant_id=resul['id'], - caller='browser' + if 'variant_validator_output' in md_response and \ + 'validation_warning_1' in md_response['variant_validator_output'] and \ + 'validation_warnings' in md_response['variant_validator_output']['validation_warning_1']: + result.append( + {'variant': line, + 'error': md_response['variant_validator_output']['validation_warning_1']['validation_warnings'][0]} ) + elif 'mobidetails_error' in md_response: + result.append({'variant': line, 'error': md_response['mobidetails_error']}) + else: + result.append({'variant': line, 'error': 'MDAPI call failed'}) + else: + result.append({'variant': line, 'error': 'Unknown gene'}) + continue + if re.search(r'^rs\d+$', line): + md_api_url = '{0}{1}'.format(request.host_url[:-1], url_for('api.api_variant_create_rs')) + # md_api_url = '{0}/api/variant/create'.format(md_api_base_url) + # print(md_api_url) + data = { + 'rs_id': line, + 'caller': 'cli', + 'api_key': api_key + } + try: + md_response = json.loads( + http.request( + 'POST', + md_api_url, + headers=header, + fields=data + ).data.decode('utf-8') + ) + for var in md_response: + # print(md_response[var]) + if 'mobidetails_error' in md_response[var]: + result.append( + {'variant': '{0} - {1}'.format(line, var), + 'error': md_response[var]['mobidetails_error']} + ) + elif var == 'mobidetails_error': + result.append({'variant': line, 'error': md_response[var]}) + else: + result.append( + {'variant': '{0} - {1}'.format(line, var), + 'id': md_response[var]['mobidetails_id'], + 'url': md_response[var]['url']} + ) + + except Exception: + result.append({'variant': line, 'error': 'MDAPI call failed'}) + continue + result.append({'variant': line, 'error': 'Bad format'}) + close_db() + flash('File correctly uploaded', 'w3-pale-green') + if g.user: + # send an email + # print(g.user) + result_list = '' + for resul in result: + result_list = '{0}
  • {1}'.format(result_list, resul['variant']) + if 'error' in resul: + result_list = '{0} - {1}
  • '.format(result_list, resul['error']) + else: + result_list = '{0} - success'.format( + result_list, + request.host_url[:-1], + url_for( + 'api.variant', + variant_id=resul['id'], + caller='browser' ) + ) - message = """ + message = """ Dear {0},

    Your batch job returned the following results:

      {1}

    You can have a direct acces to the successfully annotated variants at your profile page. """.format( - g.user['username'], - result_list, - request.host_url[:-1], - url_for( - 'auth.profile', - mobiuser_id=0 - ) - ) - md_utilities.send_email( - md_utilities.prepare_email_html( - 'MobiDetails - Batch job', - message, - False - ), - '[MobiDetails - Batch job]', - [g.user['email']] + g.user['username'], + result_list, + request.host_url[:-1], + url_for( + 'auth.profile', + mobiuser_id=0 ) - return render_template('md/variant_multiple.html', upload=result) - else: - flash('That file extension is not allowed', 'w3-pale-red') - return render_template('upload/upload_form.html') - # return redirect(request.url) + ) + md_utilities.send_email( + md_utilities.prepare_email_html( + 'MobiDetails - Batch job', + message, + False + ), + '[MobiDetails - Batch job]', + [g.user['email']] + ) + return render_template('md/variant_multiple.html', upload=result) + else: + flash('That file extension is not allowed', 'w3-pale-red') + return render_template('upload/upload_form.html') + # return redirect(request.url) return render_template("upload/upload_form.html", run_mode=md_utilities.get_running_mode()) diff --git a/tests/test_ajax.py b/tests/test_ajax.py index a40ad3cf..3c529f82 100644 --- a/tests/test_ajax.py +++ b/tests/test_ajax.py @@ -201,7 +201,13 @@ def test_lovd(client, app): res = curs.fetchall() db_pool.putconn(db) for values in res: - data_dict = dict(genome=values['genome_version'], chrom=values['chr'], g_name=values['g_name'], c_name='c.{}'.format(values['c_name']), gene=values['gene_symbol']) + data_dict = dict( + genome=values['genome_version'], + chrom=values['chr'], + g_name=values['g_name'], + c_name=f"c.{values['c_name']}", + gene=values['gene_symbol'], + ) print(data_dict) assert client.post('/lovd', data=data_dict).status_code == 200 @@ -434,20 +440,23 @@ def test_delete_variant_list(client, app, auth, list_name, return_value): assert client.get('/delete_variant_list').status_code == 404 with app.app_context(): response = client.get( - '/delete_variant_list/{}'.format(list_name), - follow_redirects=True + f'/delete_variant_list/{list_name}', follow_redirects=True ) print(response.get_data()) assert b'check_login_form' in response.get_data() # means we are in the login page auth.login(email, password) - assert client.get( - '/delete_variant_list/{}'.format(list_name), - follow_redirects=True - ).status_code == return_value - assert b'check_login_form' in client.get( - '/delete_variant_list/{}'.format(list_name), - follow_redirects=True - ).get_data() + assert ( + client.get( + f'/delete_variant_list/{list_name}', follow_redirects=True + ).status_code + == return_value + ) + assert ( + b'check_login_form' + in client.get( + f'/delete_variant_list/{list_name}', follow_redirects=True + ).get_data() + ) # test autocomplete diff --git a/tests/test_api.py b/tests/test_api.py index f8ea9736..a1a73206 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -49,7 +49,9 @@ def test_check_vv_instance(client): ('C_000001.11:g.216422237G>A', 'mobidetails_error', 'Malformed query') )) def test_api_variant_exists(client, variant, key, response): - json_response = json.loads(client.get('/api/variant/exists/{}'.format(variant)).data.decode('utf8')) + json_response = json.loads( + client.get(f'/api/variant/exists/{variant}').data.decode('utf8') + ) assert response in str(json_response[key]) # test gene @@ -62,7 +64,7 @@ def test_api_variant_exists(client, variant, key, response): ('--%USH2A', 'mobidetails_error', 'Invalid gene submitted') )) def test_api_gene(client, gene, key, response): - json_response = json.loads(client.get('/api/gene/{}'.format(gene)).data.decode('utf8')) + json_response = json.loads(client.get(f'/api/gene/{gene}').data.decode('utf8')) print(json_response) assert response in str(json_response[key]) @@ -93,7 +95,9 @@ def get_generic_api_key(): (8, 'hg19PseudoVCF', '1-216420460-C-A'), )) def test_api_variant(client, variant_id, key, value): - json_response = json.loads(client.get('/api/variant/{}/clispip/'.format(variant_id)).data.decode('utf8')) + json_response = json.loads( + client.get(f'/api/variant/{variant_id}/clispip/').data.decode('utf8') + ) assert json_response['nomenclatures'][key] == value # 2nd test variant creation @@ -116,7 +120,11 @@ def test_api_variant2(app, client): db_pool.putconn(db) for variant_id in res: print(variant_id) - json_response = json.loads(client.get('/api/variant/{}/cli/'.format(variant_id[0])).data.decode('utf8')) + json_response = json.loads( + client.get(f'/api/variant/{variant_id[0]}/cli/').data.decode( + 'utf8' + ) + ) assert 'nomenclatures' in json_response diff --git a/tests/test_auth.py b/tests/test_auth.py index 81db7051..6e1d05a7 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -96,9 +96,9 @@ def test_activate(client, mobiuser_id, api_key, message): (10, b'Username'), )) def test_profile(client, app, auth, mobiuser_id, message): - assert client.get('/auth/profile/{}'.format(mobiuser_id)).status_code == 302 + assert client.get(f'/auth/profile/{mobiuser_id}').status_code == 302 with app.app_context(): - response = client.get('/auth/profile/{}'.format(mobiuser_id), follow_redirects=True) + response = client.get(f'/auth/profile/{mobiuser_id}', follow_redirects=True) assert b'check_login_form' in response.get_data() # means we are in the login page # following does not work - as if login does not work properly? # db_pool, db = get_db() @@ -169,8 +169,4 @@ def test_reset_password(client, mobiuser_id, api_key, timestamp, message): ('test', 404), )) def test_variant_list(client, list_name, http_code): - assert client.get( - '/auth/variant_list/{}'.format( - list_name, - ) - ).status_code == http_code + assert client.get(f'/auth/variant_list/{list_name}').status_code == http_code diff --git a/tests/test_md.py b/tests/test_md.py index c7499461..0384a144 100644 --- a/tests/test_md.py +++ b/tests/test_md.py @@ -25,7 +25,7 @@ def test_gene_page(client, app): db_pool.putconn(db) for name in res: print(name[0]) - assert client.get('/gene/{}'.format(name[0])).status_code == 200 + assert client.get(f'/gene/{name[0]}').status_code == 200 def test_genes_page(client): @@ -97,8 +97,8 @@ def test_search_engine(client, app, t_search, url, status_code): print(response.get_data()) assert url in response.get_data() elif status_code == 302: - print(response.headers['Location'] + 'http://localhost/{}'.format(url)) - assert 'http://localhost/{}'.format(url) == response.headers['Location'] + print(response.headers['Location'] + f'http://localhost/{url}') + assert f'http://localhost/{url}' == response.headers['Location'] else: assert status_code == response.status_code @@ -123,4 +123,4 @@ def test_variant_page(client, app): db_pool.putconn(db) for variant_id in res: print(variant_id[0]) - assert client.get('/api/variant/{}/browser/'.format(variant_id[0])).status_code == 200 + assert client.get(f'/api/variant/{variant_id[0]}/browser/').status_code == 200 diff --git a/tests/test_md_utilities.py b/tests/test_md_utilities.py index e52bf607..5f078cbb 100644 --- a/tests/test_md_utilities.py +++ b/tests/test_md_utilities.py @@ -960,7 +960,7 @@ def test_lovd_error_html(): )) def test_format_mirs(record, result): totest = md_utilities.format_mirs(record) - print('-{}'.format(totest)) + print(f'-{totest}') assert re.split(r'\s+', result) == re.split(r'\s+', totest)