diff --git a/README b/README index fe53fef82e17ab55c0066f5481f2a13a821f0765..f345af367d61e6d2b71caf238eef0ee78fe8c492 100644 --- a/README +++ b/README @@ -25,7 +25,11 @@ The unit tests depend on a running batch daemon and SOAP webservice: Now run the tests with: - $ MUTALYZER_ENV=test nosetests + $ MUTALYZER_ENV=test nosetests -v + +Or, if you are in a hurry, skip the long-running tests with: + + $ MUTALYZER_ENV=test MUTALYZER_QUICK_TEST=1 nosetests -v Development Notes diff --git a/mutalyzer/Retriever.py b/mutalyzer/Retriever.py index 7dc6e160f1980c0b46860741a5e86ddb77597c5e..d3dd78a8d7767b96803e919f97088222e7f1ba05 100644 --- a/mutalyzer/Retriever.py +++ b/mutalyzer/Retriever.py @@ -271,8 +271,17 @@ class Retriever(object) : return [] # Query dbSNP for the SNP. - response = Entrez.efetch(db='SNP', id=id, rettype='flt', - retmode='xml') + try: + response = Entrez.efetch(db='SNP', id=id, rettype='flt', + retmode='xml') + except IOError: + # Could not parse XML. + self._output.addMessage(__file__, 4, 'EENTREZ', + 'Error connecting to dbSNP.') + self._output.addMessage(__file__, -1, 'INFO', + 'IOError: %s' % str(e)) + return [] + response_text = response.read() try: diff --git a/mutalyzer/templates/check.html b/mutalyzer/templates/check.html index 2496fd3a8d1093cce39e1f0acd5549c4053e7aef..271293e2bff8feb9fa951c69f396e97ee5e51958 100644 --- a/mutalyzer/templates/check.html +++ b/mutalyzer/templates/check.html @@ -10,7 +10,7 @@ <center> <h3>Name checker</h3> </center> - <div style="border: 1px solid grey; background: #ccffff; padding: 20px;"> + <div style="border: 1px solid grey; background-color: aliceblue; padding: 20px;"> <div id = "output" tal:condition = "interactive"> <div> Please insert the mutation name using the @@ -116,7 +116,7 @@ <div tal:condition = "transcriptInfo"> <b>Detailed information about the selected transcript:</b><br> <br> - <div style = "background-color : #ccffff; padding : 20px; border: 1px solid grey"> + <div style = "background-color : aliceblue; padding : 20px; border: 1px solid grey"> <div tal:condition = "oldProtein"> <b>Reference protein:</b><br> <pre><div tal:repeat = "i oldProtein" diff --git a/mutalyzer/util.py b/mutalyzer/util.py index af4b092a65a8b5e423dcbcf651a5d09fa088aa3c..892bde65f623843bdff3b2354b333b62f1734944 100644 --- a/mutalyzer/util.py +++ b/mutalyzer/util.py @@ -19,6 +19,7 @@ General utility functions. """ +import os import math import time from itertools import izip_longest @@ -719,3 +720,22 @@ def message_info(message): 'class': classes[message.level], 'description': message.description} #message_info + + +def slow(f): + """ + Decorator for slow tests. This makes them to pass immediately, without + running them. But only if the environment variable MUTALYZER_QUICK_TEST + is 1. + + @todo: I don't think this actually belongs here (a separate util module + for the unit tests?). + """ + def slow_f(*args, **kwargs): + if 'MUTALYZER_QUICK_TEST' in os.environ \ + and os.environ['MUTALYZER_QUICK_TEST'] == '1': + return + else: + f(*args, **kwargs) + return slow_f +#slow diff --git a/mutalyzer/webservice.py b/mutalyzer/webservice.py index 2787ecb215a1c184414d33b53b2a2698823e6f2a..952063164eb8ae1d69cec46c1990129d5904c757 100644 --- a/mutalyzer/webservice.py +++ b/mutalyzer/webservice.py @@ -55,14 +55,18 @@ from mutalyzer import GenRecord from mutalyzer.models import * -class MutalyzerService(DefinitionBase) : +class MutalyzerService(DefinitionBase): """ Mutalyzer webservices. These methods are made public via a SOAP interface. """ + def __init__(self, environ=None): + self._config = Config() + super(MutalyzerService, self).__init__(environ) + #__init__ - def __checkBuild(self, L, build, config) : + def __checkBuild(self, L, build) : """ Check if the build is supported (hg18 or hg19). @@ -73,11 +77,9 @@ class MutalyzerService(DefinitionBase) : @type L: object @arg build: The human genome build name that needs to be checked. @type build: string - @arg config: Configuration object of the Db module. - @type config: object """ - if not build in config.dbNames : + if not build in self._config.Db.dbNames : L.addMessage(__file__, 4, "EARG", "EARG %s" % build) raise Fault("EARG", "The build argument (%s) was not a valid " \ @@ -169,16 +171,14 @@ class MutalyzerService(DefinitionBase) : @return: A list of transcripts. @rtype: list """ - - C = Config() - L = Output(__file__, C.Output) + L = Output(__file__, self._config.Output) L.addMessage(__file__, -1, "INFO", "Received request getTranscripts(%s %s %s)" % (build, chrom, pos)) - self.__checkBuild(L, build, C.Db) - D = Db.Mapping(build, C.Db) + self.__checkBuild(L, build) + D = Db.Mapping(build, self._config.Db) self.__checkChrom(L, D, chrom) self.__checkPos(L, pos) @@ -196,25 +196,23 @@ class MutalyzerService(DefinitionBase) : L.addMessage(__file__, -1, "INFO", "We return %s" % ret) - del D, L, C + del D, L return ret #getTranscripts @soap(Mandatory.String, Mandatory.String, _returns = Array(Mandatory.String)) - def getTranscriptsByGeneName(self, build, name) : + def getTranscriptsByGeneName(self, build, name): """ Todo: documentation. """ - - C = Config() - L = Output(__file__, C.Output) + L = Output(__file__, self._config.Output) L.addMessage(__file__, -1, "INFO", "Received request getTranscriptsByGene(%s %s)" % (build, name)) - self.__checkBuild(L, build, C.Db) - D = Db.Mapping(build, C.Db) + self.__checkBuild(L, build) + D = Db.Mapping(build, self._config.Db) ret = D.get_TranscriptsByGeneName(name) @@ -247,16 +245,14 @@ class MutalyzerService(DefinitionBase) : @return: A list of transcripts. @rtype: list """ - - C = Config() - L = Output(__file__, C.Output) + L = Output(__file__, self._config.Output) L.addMessage(__file__, -1, "INFO", "Received request getTranscriptsRange(%s %s %s %s %s)" % (build, chrom, pos1, pos2, method)) - D = Db.Mapping(build, C.Db) - self.__checkBuild(L, build, C.Db) + D = Db.Mapping(build, self._config.Db) + self.__checkBuild(L, build) ret = D.get_Transcripts(chrom, pos1, pos2, method) @@ -267,7 +263,7 @@ class MutalyzerService(DefinitionBase) : "Finished processing getTranscriptsRange(%s %s %s %s %s)" % ( build, chrom, pos1, pos2, method)) - del D, L, C + del D, L return ret #getTranscriptsRange @@ -284,22 +280,20 @@ class MutalyzerService(DefinitionBase) : @return: The name of the associated gene. @rtype: string """ - - C = Config() - L = Output(__file__, C.Output) + L = Output(__file__, self._config.Output) L.addMessage(__file__, -1, "INFO", "Received request getGeneName(%s %s)" % (build, accno)) - D = Db.Mapping(build, C.Db) - self.__checkBuild(L, build, C.Db) + D = Db.Mapping(build, self._config.Db) + self.__checkBuild(L, build) ret = D.get_GeneName(accno.split('.')[0]) L.addMessage(__file__, -1, "INFO", "Finished processing getGeneName(%s %s)" % (build, accno)) - del D, L, C + del D, L return ret #getGeneName @@ -345,22 +339,20 @@ class MutalyzerService(DefinitionBase) : - type ; The mutation type. @rtype: object """ - - C = Config() - L = Output(__file__, C.Output) + L = Output(__file__, self._config.Output) L.addMessage(__file__, -1, "INFO", "Reveived request mappingInfo(%s %s %s %s)" % ( LOVD_ver, build, accNo, variant)) - conv = Mapper.Converter(build, C, L) + conv = Mapper.Converter(build, self._config, L) result = conv.mainMapping(accNo, variant) L.addMessage(__file__, -1, "INFO", "Finished processing mappingInfo(%s %s %s %s)" % ( LOVD_ver, build, accNo, variant)) - del L, C + del L return result #mappingInfo @@ -385,15 +377,13 @@ class MutalyzerService(DefinitionBase) : - CDS_stop ; CDS stop in I{c.} notation. @rtype: object """ - - C = Config() - O = Output(__file__, C.Output) + O = Output(__file__, self._config.Output) O.addMessage(__file__, -1, "INFO", "Received request transcriptInfo(%s %s %s)" % (LOVD_ver, build, accNo)) - converter = Mapper.Converter(build, C, O) + converter = Mapper.Converter(build, self._config, O) T = converter.mainTranscript(accNo) O.addMessage(__file__, -1, "INFO", @@ -415,14 +405,13 @@ class MutalyzerService(DefinitionBase) : @return: The accession number of a chromosome. @rtype: string """ - C = Config() # Read the configuration file. - D = Db.Mapping(build, C.Db) - L = Output(__file__, C.Output) + D = Db.Mapping(build, self._config.Db) + L = Output(__file__, self._config.Output) L.addMessage(__file__, -1, "INFO", "Received request chromAccession(%s %s)" % (build, name)) - self.__checkBuild(L, build, C.Db) + self.__checkBuild(L, build) self.__checkChrom(L, D, name) result = D.chromAcc(name) @@ -431,7 +420,7 @@ class MutalyzerService(DefinitionBase) : "Finished processing chromAccession(%s %s)" % (build, name)) - del D,L,C + del D,L return result #chromAccession @@ -448,14 +437,13 @@ class MutalyzerService(DefinitionBase) : @return: The name of a chromosome. @rtype: string """ - C = Config() # Read the configuration file. - D = Db.Mapping(build, C.Db) - L = Output(__file__, C.Output) + D = Db.Mapping(build, self._config.Db) + L = Output(__file__, self._config.Output) L.addMessage(__file__, -1, "INFO", "Received request chromName(%s %s)" % (build, accNo)) - self.__checkBuild(L, build, C.Db) + self.__checkBuild(L, build) # self.__checkChrom(L, D, name) result = D.chromName(accNo) @@ -464,7 +452,7 @@ class MutalyzerService(DefinitionBase) : "Finished processing chromName(%s %s)" % (build, accNo)) - del D,L,C + del D,L return result #chromosomeName @@ -481,14 +469,13 @@ class MutalyzerService(DefinitionBase) : @return: The name of a chromosome. @rtype: string """ - C = Config() # Read the configuration file. - D = Db.Mapping(build, C.Db) - L = Output(__file__, C.Output) + D = Db.Mapping(build, self._config.Db) + L = Output(__file__, self._config.Output) L.addMessage(__file__, -1, "INFO", "Received request getchromName(%s %s)" % (build, acc)) - self.__checkBuild(L, build, C.Db) + self.__checkBuild(L, build) # self.__checkChrom(L, D, name) result = D.get_chromName(acc) @@ -497,7 +484,7 @@ class MutalyzerService(DefinitionBase) : "Finished processing getchromName(%s %s)" % (build, acc)) - del D,L,C + del D,L return result #chromosomeName @@ -516,14 +503,12 @@ class MutalyzerService(DefinitionBase) : @return: The variant(s) in either I{g.} or I{c.} notation. @rtype: list """ - - C = Config() # Read the configuration file. - D = Db.Mapping(build, C.Db) - O = Output(__file__, C.Output) + D = Db.Mapping(build, self._config.Db) + O = Output(__file__, self._config.Output) O.addMessage(__file__, -1, "INFO", "Received request cTogConversion(%s %s)" % ( build, variant)) - converter = Mapper.Converter(build, C, O) + converter = Mapper.Converter(build, self._config, O) variant = converter.correctChrVariant(variant) if "c." in variant : @@ -553,8 +538,7 @@ class MutalyzerService(DefinitionBase) : - messages: List of (error) messages as strings. @rtype: object """ - C = Config() # Read the configuration file. - output = Output(__file__, C.Output) + output = Output(__file__, self._config.Output) output.addMessage(__file__, -1, "INFO", "Received request checkSyntax(%s)" % (variant)) @@ -584,12 +568,10 @@ class MutalyzerService(DefinitionBase) : """ Todo: documentation. """ - C = Config() # Read the configuration file. - O = Output(__file__, C.Output) + O = Output(__file__, self._config.Output) O.addMessage(__file__, -1, "INFO", "Received request runMutalyzer(%s)" % (variant)) - #Mutalyzer.process(variant, C, O) - variantchecker.check_variant(variant, C, O) + variantchecker.check_variant(variant, self._config, O) result = MutalyzerOutput() @@ -647,17 +629,16 @@ class MutalyzerService(DefinitionBase) : """ Todo: documentation. """ - C = Config() - O = Output(__file__, C.Output) - D = Db.Cache(C.Db) + O = Output(__file__, self._config.Output) + D = Db.Cache(self._config.Db) O.addMessage(__file__, -1, "INFO", "Received request getGeneAndTranscript(%s, %s)" % (genomicReference, transcriptReference)) - retriever = Retriever.GenBankRetriever(C.Retriever, O, D) + retriever = Retriever.GenBankRetriever(self._config.Retriever, O, D) record = retriever.loadrecord(genomicReference) - GenRecordInstance = GenRecord.GenRecord(O, C.GenRecord) + GenRecordInstance = GenRecord.GenRecord(O, self._config.GenRecord) GenRecordInstance.record = record GenRecordInstance.checkRecord() @@ -714,17 +695,16 @@ class MutalyzerService(DefinitionBase) : - id - product """ - C = Config() - O = Output(__file__, C.Output) - D = Db.Cache(C.Db) + O = Output(__file__, self._config.Output) + D = Db.Cache(self._config.Db) O.addMessage(__file__, -1, "INFO", "Received request getTranscriptsAndInfo(%s)" % genomicReference) - retriever = Retriever.GenBankRetriever(C.Retriever, O, D) + retriever = Retriever.GenBankRetriever(self._config.Retriever, O, D) record = retriever.loadrecord(genomicReference) # Todo: If loadRecord failed (e.g. DTD missing), we should abort here. - GenRecordInstance = GenRecord.GenRecord(O, C.GenRecord) + GenRecordInstance = GenRecord.GenRecord(O, self._config.GenRecord) GenRecordInstance.record = record GenRecordInstance.checkRecord() @@ -829,11 +809,9 @@ class MutalyzerService(DefinitionBase) : """ Todo: documentation, error handling, argument checking, tests. """ - - C = Config() - O = Output(__file__, C.Output) - D = Db.Cache(C.Db) - retriever = Retriever.GenBankRetriever(C.Retriever, O, D) + O = Output(__file__, self._config.Output) + D = Db.Cache(self._config.Db) + retriever = Retriever.GenBankRetriever(self._config.Retriever, O, D) O.addMessage(__file__, -1, "INFO", "Received request sliceChromosomeByGene(%s, %s, %s, %s)" % ( @@ -860,10 +838,9 @@ class MutalyzerService(DefinitionBase) : """ Todo: documentation, error handling, argument checking, tests. """ - C = Config() - O = Output(__file__, C.Output) - D = Db.Cache(C.Db) - retriever = Retriever.GenBankRetriever(C.Retriever, O, D) + O = Output(__file__, self._config.Output) + D = Db.Cache(self._config.Db) + retriever = Retriever.GenBankRetriever(self._config.Retriever, O, D) O.addMessage(__file__, -1, "INFO", "Received request sliceChromosome(%s, %s, %s, %s)" % ( diff --git a/tests/test_mutalyzer.py b/tests/test_mutalyzer.py index 68fd7cc5b672a949b157186c0a960d367444ee05..ddbaf0c326b845ded3f2e11aab8d105e140cfc07 100644 --- a/tests/test_mutalyzer.py +++ b/tests/test_mutalyzer.py @@ -105,7 +105,6 @@ class TestMutalyzer(): assert 'AL449423.14(CDKN2A_v001):c.99_100insTAG' in self.output.getOutput('descriptions') assert_equal ('AL449423.14:g.65471_65472insACT', self.output.getIndexedOutput('genomicDescription', 0, '')) assert len(self.output.getMessagesWithErrorCode('WROLLFORWARD')) == 1 - assert len(self.output.getMessagesWithErrorCode('WROLLREVERSE')) == 1 def test_roll_reverse_ins(self): """ @@ -116,6 +115,23 @@ class TestMutalyzer(): assert 'AL449423.14(CDKN2A_v001):c.99_100insTAG' in self.output.getOutput('descriptions') assert_equal ('AL449423.14:g.65471_65472insACT', self.output.getIndexedOutput('genomicDescription', 0, '')) assert len(self.output.getMessagesWithErrorCode('WROLLFORWARD')) == 0 + + def test_roll_message_forward(self): + """ + Roll warning message should only be shown for currently selected + strand (forward). + """ + check_variant('AL449423.14:g.65470_65471insTAC', self.config, self.output) + assert len(self.output.getMessagesWithErrorCode('WROLLFORWARD')) == 1 + assert len(self.output.getMessagesWithErrorCode('WROLLREVERSE')) == 0 + + def test_roll_message_reverse(self): + """ + Roll warning message should only be shown for currently selected + strand (reverse). + """ + check_variant('AL449423.14(CDKN2A_v001):c.98_99insGTA', self.config, self.output) + assert len(self.output.getMessagesWithErrorCode('WROLLFORWARD')) == 0 assert len(self.output.getMessagesWithErrorCode('WROLLREVERSE')) == 1 def test_ins_cds_start(self): diff --git a/tests/test_webservice.py b/tests/test_webservice.py index 05e29713066666703a7197d8a1fd4c557f314952..5d9e08ff6af240924f0ba65afc13ed94f3286032 100644 --- a/tests/test_webservice.py +++ b/tests/test_webservice.py @@ -3,6 +3,19 @@ Tests for the SOAP interface to Mutalyzer. """ +# Monkey patch suds, because for some weird reason the location +# http://www.w3.org/2001/xml.xsd is used for the XML namespace, but the W3C +# seems to respond too slow on that url. We use therefore use +# http://www.w3.org/2009/01/xml.xsd which fixes this. +from suds.xsd.sxbasic import Import +_import_open = Import.open +def _import_open_patched(self, *args, **kwargs): + if self.location == 'http://www.w3.org/2001/xml.xsd': + self.location = 'http://www.w3.org/2009/01/xml.xsd' + return _import_open(self, *args, **kwargs) +Import.open = _import_open_patched + + import os import logging; logging.raiseExceptions = 0 import urllib2 diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py index 5feea15704bd04ac4e78ce84acaf158e3be5e6ab..7ab668c847dd3d2d5271e2ff97fe8365bac12aee 100644 --- a/tests/test_wsgi.py +++ b/tests/test_wsgi.py @@ -20,6 +20,7 @@ from webtest import TestApp import mutalyzer from mutalyzer.wsgi import application +from mutalyzer.util import slow class TestWSGI(): @@ -235,6 +236,7 @@ class TestWSGI(): r = form.submit() r.mustcontain('NM_003002.2:c.204C>T') + @slow def _batch(self, batch_type='NameChecker', arg1=None, file="", size=0, header='', lines=None): """ @@ -414,6 +416,7 @@ class TestWSGI(): size=len(variants)-1, header='Input\tStatus') + @slow def test_batch_syntaxchecker_toobig(self): """ Submit the batch syntax checker with a too big input file. @@ -440,6 +443,7 @@ facilisi.""" r = form.submit(status=413) assert_equal(r.content_type, 'text/plain') + @slow def test_batch_multicolumn(self): """ Submit the batch syntax checker with a multiple-colums input file.