Skip to content

Commit f61fe1b

Browse files
author
Peter J. Freeman
authored
Merge pull request #70 from openvar/develop_2_0
begin creating fast validation endpoints - currently variants in jour…
2 parents 57ee3bc + 4b4247c commit f61fe1b

File tree

5 files changed

+289
-4
lines changed

5 files changed

+289
-4
lines changed

rest_VariantValidator/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
logHandler = handlers.RotatingFileHandler(str(parent) + '/rest_VariantValidator.log',
4040
maxBytes=500000,
4141
backupCount=2)
42+
4243
# We want to minimise the amount of information we log to capturing bugs
4344
file_level = config['logging']['file'].upper()
4445
log_file_level = logging.getLevelName(file_level)

rest_VariantValidator/endpoints/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .variantvalidator_endpoints import api as ns_vv
77
from .variantformatter_endpoints import api as ns_vf
88
from .lovd_endpoints import api as ns_lovd
9+
from .vijs_endpoints import api as ns_vijs
910
from .hello import api as ns_hello
1011

1112
# Obtain VariantValidator related metadata
@@ -29,8 +30,8 @@ def specs_url(self):
2930

3031
# Define the API as api
3132
api = CustomAPI(version=rest_VariantValidator.__version__,
32-
title="rest_VariantValidator",
33-
description="## By continuing to use this service you agree to our terms and conditions of Use\n"
33+
title="rest_VariantValidator",
34+
description="## By continuing to use this service you agree to our terms and conditions of Use\n"
3435
"- [Terms and Conditions](https://github.com/openvar/variantValidator/blob"
3536
"/master/README.md)\n\n"
3637
"## Powered by\n"
@@ -50,6 +51,7 @@ def specs_url(self):
5051
api.add_namespace(ns_vv)
5152
api.add_namespace(ns_vf)
5253
api.add_namespace(ns_lovd)
54+
api.add_namespace(ns_vijs)
5355
api.add_namespace(ns_hello)
5456

5557

rest_VariantValidator/endpoints/lovd_endpoints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"> - True - (liftover to all genomic loci)\n"
5050
"> - primary - (lift to primary assembly only)\n"
5151
"> - False")
52-
class VariantValidatorClass(Resource):
52+
class LOVDClass(Resource):
5353
# Add documentation about the parser
5454
@api.expect(parser, validate=True)
5555
def get(self, genome_build, variant_description, transcript_model, select_transcripts, checkonly, liftover):

rest_VariantValidator/endpoints/variantformatter_endpoints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"> - True (return ONLY the genomic variant descriptions and not transcript and protein"
4141
" descriptions)\n"
4242
"> - False")
43-
class VariantValidatorClass(Resource):
43+
class VariantFormatterClass(Resource):
4444
# Add documentation about the parser
4545
@api.expect(parser, validate=True)
4646
def get(self, genome_build, variant_description, transcript_model, select_transcripts, checkonly):
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
# Import modules
2+
from flask_restplus import Namespace, Resource
3+
from . import request_parser
4+
from . import representations
5+
6+
# Import variantFormatter
7+
import VariantFormatter
8+
import VariantFormatter.simpleVariantFormatter
9+
import VariantValidator
10+
vval = VariantValidator.Validator()
11+
12+
"""
13+
Create a list containing common warnings which are not errors
14+
"""
15+
my_warnings = [
16+
"A more recent version of the selected reference sequence",
17+
"No transcripts found that fully overlap the described variation",
18+
"is pending therefore changes may be made to the LRG reference sequence",
19+
"This coding sequence variant description spans at least one intron",
20+
"RefSeqGene record not available",
21+
"automapped to equivalent RefSeq record",
22+
"Protein level variant descriptions are not fully supported",
23+
"is HGVS compliant and contains a valid reference amino acid description"
24+
]
25+
26+
27+
"""
28+
Create a parser object locally
29+
"""
30+
parser = request_parser.parser
31+
32+
api = Namespace('VariantsInJournals', description='Endpoints to ensure variants submitted to journals are validated')
33+
34+
35+
@api.route("/transcript_descriptions/<string:genome_build>/<string:variant_description>")
36+
@api.param("variant_description", "***HGVS***\n"
37+
"> - NM_000088.3:c.589G>T\n"
38+
"> - LRG_1t1:c.589G>T\n"
39+
"> - *Recommended maximum is 60 variants per submission*\n")
40+
@api.param("genome_build", "***Accepted:***\n"
41+
"> - GRCh37\n"
42+
"> - GRCh38\n"
43+
"> - hg19\n"
44+
"> - hg38")
45+
class VariantValidatorClass(Resource):
46+
# Add documentation about the parser
47+
@api.expect(parser, validate=True)
48+
def get(self, genome_build, variant_description):
49+
50+
# List the submitted descriptions and the allowed reference sequence types
51+
description_list = variant_description.split()
52+
allowed_references = ["NM_",
53+
"NR_",
54+
"ENST",
55+
"NP_",
56+
"ENSP",
57+
"t",
58+
"p"]
59+
60+
# Check correct variant type (Currently transcript but also accepts Protein
61+
# even though we do not advertise this
62+
variant_description_outs = []
63+
for description in description_list:
64+
for reference in allowed_references:
65+
if reference in description:
66+
variant_description_outs.append(description)
67+
variant_description = "|".join(variant_description_outs)
68+
69+
# Refresh the content
70+
refreshed_content = {}
71+
if variant_description is "":
72+
refreshed_content = {"error": "Unsupported variant type"}
73+
else:
74+
# Validate using the VariantValidator Python Library
75+
validate = vval.validate(variant_description, genome_build, select_transcripts='all')
76+
content = validate.format_as_dict(with_meta=True)
77+
78+
# Collect Arguments
79+
args = parser.parse_args()
80+
for k, v in content.items():
81+
if k is "metadata":
82+
refreshed_content[k] = v
83+
elif k is "flag":
84+
continue
85+
else:
86+
refreshed_content[v["submitted_variant"]] = {}
87+
refreshed_content[v["submitted_variant"]]['pass'] = False
88+
refreshed_content[v["submitted_variant"]]['errors'] = []
89+
refreshed_content[v["submitted_variant"]]['correction'] = None
90+
91+
# Handle transcript variant inputs
92+
if "p." not in v["submitted_variant"]:
93+
# Filter the errors/warnings i.e. removing any warnings that are not actual errors
94+
# Warnings which are not errors are contained in the my_warnings list
95+
error_found = []
96+
if v["validation_warnings"] is not []:
97+
for warning in v["validation_warnings"]:
98+
safe_found = False
99+
for safe in my_warnings:
100+
if safe in warning:
101+
safe_found = True
102+
break
103+
if safe_found is False:
104+
error_found.append(warning)
105+
refreshed_content[v["submitted_variant"]]['errors'] = error_found
106+
107+
# Is the input == to the output?
108+
if "LRG" not in v["submitted_variant"]:
109+
if v["submitted_variant"] == v["hgvs_transcript_variant"]:
110+
refreshed_content[v["submitted_variant"]]['pass'] = True
111+
else:
112+
refreshed_content[v["submitted_variant"]]['correction'] = v["hgvs_transcript_variant"]
113+
else:
114+
if v["submitted_variant"] == v["hgvs_lrg_transcript_variant"]:
115+
refreshed_content[v["submitted_variant"]]['pass'] = True
116+
else:
117+
refreshed_content[v["submitted_variant"]]['correction'] = v["hgvs_lrg_transcript"
118+
"_variant"]
119+
120+
else:
121+
# Filter the errors/warnings i.e. removing any warnings that are not actual errors
122+
# Warnings which are not errors are contained in the my_warnings list
123+
error_found = []
124+
if v["validation_warnings"] is not []:
125+
for warning in v["validation_warnings"]:
126+
safe_found = False
127+
for safe in my_warnings:
128+
if safe in warning:
129+
safe_found = True
130+
break
131+
if safe_found is False:
132+
error_found.append(warning)
133+
refreshed_content[v["submitted_variant"]]['errors'] = error_found
134+
135+
# Is the input == to the output?
136+
if "LRG" not in v["submitted_variant"]:
137+
if v["submitted_variant"] == v["hgvs_predicted_protein_consequence"]["tlr"]:
138+
refreshed_content[v["submitted_variant"]]['pass'] = True
139+
elif v["submitted_variant"] == v["hgvs_predicted_protein_consequence"]["slr"]:
140+
refreshed_content[v["submitted_variant"]]['pass'] = True
141+
else:
142+
if v["hgvs_predicted_protein_consequence"]["tlr"] != "":
143+
refreshed_content[v["submitted_variant"]]['correction'] = v[
144+
"hgvs_predicted_protein_consequence"]["tlr"]
145+
else:
146+
refreshed_content[v["submitted_variant"]]['correction'] = None
147+
else:
148+
if v["submitted_variant"] == v["hgvs_predicted_protein_consequence"]["lrg_tlr"]:
149+
refreshed_content[v["submitted_variant"]]['pass'] = True
150+
elif v["submitted_variant"] == v["hgvs_predicted_protein_consequence"]["lrg_slr"]:
151+
refreshed_content[v["submitted_variant"]]['pass'] = True
152+
else:
153+
if v["hgvs_predicted_protein_consequence"]["lrg_tlr"] != "":
154+
refreshed_content[v["submitted_variant"]]['correction'] = v[
155+
"hgvs_predicted_protein_consequence"]["lrg_tlr"]
156+
else:
157+
refreshed_content[v["submitted_variant"]]['correction'] = None
158+
159+
# Overrides the default response route so that the standard HTML URL can return any specified format
160+
if args['content-type'] == 'application/json':
161+
# example: http://127.0.0.1:5000.....bob?content-type=application/json
162+
return representations.application_json(refreshed_content, 200, None)
163+
# example: http://127.0.0.1:5000.....?content-type=application/xml
164+
elif args['content-type'] == 'application/xml':
165+
return representations.xml(refreshed_content, 200, None)
166+
else:
167+
# Return the api default output
168+
return refreshed_content
169+
170+
171+
@api.route("/genomic_descriptions/<string:genome_build>/<string:variant_description>/<string:transcript_model>")
172+
@api.param("variant_description", "***Genomic HGVS***\n"
173+
"> - NC_000017.10:g.48275363C>A\n"
174+
"> - *Recommended maximum is 60 variants per submission*\n")
175+
@api.param("transcript_model", "***Accepted:***\n"
176+
"> - refseq (return data for RefSeq transcript models)\n"
177+
"> - all (currently refseq only)")
178+
@api.param("genome_build", "***Accepted:***\n"
179+
"> - GRCh37\n"
180+
"> - GRCh38\n"
181+
"> - hg19\n"
182+
"> - hg38\n")
183+
class LOVDClass(Resource):
184+
# Add documentation about the parser
185+
@api.expect(parser, validate=True)
186+
def get(self, genome_build, variant_description, transcript_model):
187+
if transcript_model == 'None' or transcript_model == 'none':
188+
transcript_model = None
189+
select_transcripts = None
190+
checkonly = True
191+
liftover = False
192+
193+
# List the submitted descriptions and the allowed reference sequence types
194+
description_list = variant_description.split()
195+
allowed_references = ["NC_",
196+
"NG_"]
197+
198+
# Check correct variant type (Currently transcript but also accepts Protein
199+
# even though we do not advertise this
200+
variant_description_outs = []
201+
for description in description_list:
202+
for reference in allowed_references:
203+
if reference in description:
204+
variant_description_outs.append(description)
205+
variant_description = "|".join(variant_description_outs)
206+
207+
# Refresh the content
208+
refreshed_content = {}
209+
if variant_description is "":
210+
refreshed_content = {"error": "Unsupported variant type"}
211+
else:
212+
# Validate using the VariantValidator Python Library
213+
content = VariantFormatter.simpleVariantFormatter.format(variant_description,
214+
genome_build,
215+
transcript_model,
216+
select_transcripts,
217+
checkonly,
218+
liftover)
219+
220+
# Collect Arguments
221+
args = parser.parse_args()
222+
for k, v in content.items():
223+
if k is "metadata":
224+
refreshed_content[k] = v
225+
else:
226+
for k2, v2 in v.items():
227+
if k2 is "flag" or k2 is "errors":
228+
continue
229+
230+
# else
231+
refreshed_content[k2] = {}
232+
refreshed_content[k2]['pass'] = False
233+
refreshed_content[k2]['errors'] = []
234+
refreshed_content[k2]['correction'] = None
235+
236+
# Filter the errors/warnings i.e. removing any warnings that are not actual errors
237+
# Warnings which are not errors are contained in the my_warnings list
238+
error_found = []
239+
if v2["genomic_variant_error"] is not None:
240+
safe_found = False
241+
for safe in my_warnings:
242+
if safe in v2["genomic_variant_error"]:
243+
safe_found = True
244+
break
245+
if safe_found is False:
246+
error_found.append(v2["genomic_variant_error"])
247+
refreshed_content[k2]['errors'] = error_found
248+
249+
# Populate the rest of the output
250+
if k2 == v2["g_hgvs"]:
251+
refreshed_content[k2]["pass"] = True
252+
else:
253+
refreshed_content[k2]["correction"] = v2["g_hgvs"]
254+
255+
# Overrides the default response route so that the standard HTML URL can return any specified format
256+
if args['content-type'] == 'application/json':
257+
# example: http://127.0.0.1:5000.....bob?content-type=application/json
258+
return representations.application_json(refreshed_content, 200, None)
259+
# example: http://127.0.0.1:5000.....?content-type=application/xml
260+
elif args['content-type'] == 'application/xml':
261+
return representations.xml(refreshed_content, 200, None)
262+
else:
263+
# Return the api default output
264+
return refreshed_content
265+
266+
267+
# <LICENSE>
268+
# Copyright (C) 2016-2021 VariantValidator Contributors
269+
#
270+
# This program is free software: you can redistribute it and/or modify
271+
# it under the terms of the GNU Affero General Public License as
272+
# published by the Free Software Foundation, either version 3 of the
273+
# License, or (at your option) any later version.
274+
#
275+
# This program is distributed in the hope that it will be useful,
276+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
277+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
278+
# GNU Affero General Public License for more details.
279+
#
280+
# You should have received a copy of the GNU Affero General Public License
281+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
282+
# </LICENSE>

0 commit comments

Comments
 (0)