BlastHTTP 1.0.1 → 1.2.0
raw patch · 4 files changed
+184/−66 lines, 4 filesdep +biofasta
Dependencies added: biofasta
Files
- BlastHTTP.cabal +9/−6
- README.md +18/−0
- changelog +7/−0
- src/Bio/BlastHTTP.hs +150/−60
BlastHTTP.cabal view
@@ -7,7 +7,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 1.0.1+version: 1.2.0 synopsis: Libary to interface with the NCBI blast REST interface description: Searches for a provided nucleotide or protein sequence with the NCBI Blast REST service and returns a blast result in xml format as BlastResult datatype. .@@ -19,7 +19,7 @@ license: GPL-3 license-file: LICENSE author: Florian Eggenhofer-maintainer: egg@tbi.univie.ac.at+maintainer: florian.eggenhofer@univie.ac.at -- copyright: homepage: https://github.com/eggzilla/BlastHTTP category: Bioinformatics@@ -27,14 +27,17 @@ build-type: Simple cabal-version: >=1.8 +extra-source-files:+ README.md changelog+ source-repository head type: git location: https://github.com/eggzilla/BlastHTTP source-repository this type: darcs- location: https://github.com/eggzilla/BlastHTTP/tree/v1.0.0- tag: v1.0.0+ location: https://github.com/eggzilla/BlastHTTP/tree/1.0.1+ tag: 1.0.1 library -- Modules exported by the library.@@ -44,7 +47,7 @@ -- other-modules: -- Other library packages from which modules are imported.- build-depends: base >=4.5 && <5, transformers, bytestring, conduit, HTTP, http-conduit, hxt, network, mtl, blastxml, biocore- + build-depends: base >=4.5 && <5, transformers, bytestring, conduit, HTTP, http-conduit, hxt, network, mtl, blastxml, biocore, biofasta+ ghc-options: -Wall -O2 -- Directories containing source files. hs-source-dirs: src
+ README.md view
@@ -0,0 +1,18 @@+BlastHTTP [](https://travis-ci.org/eggzilla/BlastHTTP)+=========++Haskell cabal libary for submission and result retrieval from the NCBI Blast REST webservice++Changes:++Version 1.0.0:++Added optionalArgument parameter to BlastHTTP datatype and removed EntrezQuery.+This has the advantage that any additional argument can be supplied,+not only EntrezQuery strings.++Version 0.0.1:++Initial Version++
+ changelog view
@@ -0,0 +1,7 @@+-*-change-log-*-+1.0.2 Florian Eggenhofer <florian.eggenhofer@univie.ac.at> 20. April 2015+ * Added experimental support for the european bioinformatics institute blast REST interface+ * Added support for multiple sequences in one request+1.0.1 Florian Eggenhofer <florian.eggenhofer@univie.ac.at> 09. October 2014+ * Added README.md, travis CI support+ * Added new optionalArgument parameter enabling submission of optional blast search parameters, like e-value cutoff
src/Bio/BlastHTTP.hs view
@@ -8,119 +8,209 @@ -- -- 2. database: Selects the database to be queried against. Example values are refseq_genomic, nr, est,.. Please consider that the database must be chosen in accordance with the blastprogram. Default value: refseq_genomic. Type: Maybe String ----- 3. querySequence: nucleotides or protein sequence, depending on the blast program used. If no sequence is provided an exception as String will be produced. Type: Maybe SeqData+-- 3. querySequences: nucleotides or protein sequences, depending on the blast program used. If no sequence is provided an exception as String will be produced. Type: [Sequence] ----- 4. entrezQuery: This argument is optional and will filter the result if provided. Type: Maybe String+-- 4. optionalArguments: This argument is optional and will filter the result if provided. Type: Maybe String --+-- 5. optionalWalltime: Optional walltime in mircroseconds. If specified, will terminate the query after reaching the timelimit and return Left. Type: Maybe Int+-- -- and returns Either a BlastResult (Right) on success or an exception as String (Left) -- -- If you plan to submit more than 20 searches in one session, please look up the Usage Guidelines in the webservice information <http://www.ncbi.nlm.nih.gov/BLAST/developer.shtml>. module Bio.BlastHTTP ( BlastHTTPQuery (..), blastHTTP) where -import Network.HTTP.Conduit -import Data.Conduit -import qualified Data.ByteString.Lazy.Char8 as L8-import Control.Monad.IO.Class (liftIO) +import Network.HTTP.Conduit +import qualified Data.ByteString.Lazy.Char8 as L8 +import qualified Data.ByteString.Char8 as B import qualified Control.Monad as CM import Bio.BlastXML import Text.XML.HXT.Core import Network-import qualified Data.Conduit.List as CL import Data.List-import Control.Monad.Error as CM import Control.Concurrent import Data.Maybe-import Data.Either import Bio.Core.Sequence+import Bio.Sequence.Fasta+import Network.HTTP.Base data BlastHTTPQuery = BlastHTTPQuery - { program :: Maybe String+ { provider :: Maybe String+ , program :: Maybe String , database :: Maybe String- , querySequence :: Maybe SeqData- , optionalArguments :: Maybe String + , querySequences :: [Sequence]+ , optionalArguments :: Maybe String+ , optionalWalltime :: Maybe Int } deriving (Show, Eq) -- | Parse HTML results into Xml Tree datastructure parseHTML :: String -> IOStateArrow s0 b0 XmlTree parseHTML = readString [withParseHTML yes, withWarnings no] --- | Gets all subtrees with the specified id attribute-atName :: ArrowXml a => String -> a XmlTree XmlTree-atName elementId = deep (isElem >>> hasAttrValue "name" (== elementId)) -- | Gets all subtrees with the specified id attribute atId :: ArrowXml a => String -> a XmlTree XmlTree atId elementId = deep (isElem >>> hasAttrValue "id" (== elementId)) -- | Send query and parse RID from retrieved HTML -startSession :: String -> String -> String -> Maybe String -> IO String-startSession program database querySequence optionalArguments = do+startSession :: String -> String -> String -> String -> Maybe String -> IO String+startSession provider' program' database' querySequences' optionalArguments'+ | provider' == "ebi" = startSessionEBI program' database' querySequences' optionalArguments' + | otherwise = startSessionNCBI program' database' querySequences' optionalArguments' ++startSessionEBI :: String -> String -> String -> Maybe String -> IO String+startSessionEBI program' database' querySequences' optionalArguments' = do requestXml <- withSocketsDo- $ sendQuery program database querySequence optionalArguments+ $ sendQueryEBI program' database' querySequences' optionalArguments'+ let requestID = L8.unpack requestXml+ return requestID++startSessionNCBI :: String -> String -> String -> Maybe String -> IO String+startSessionNCBI program' database' querySequences' optionalArguments' = do+ requestXml <- withSocketsDo+ $ sendQueryNCBI program' database' querySequences' optionalArguments' let requestXMLString = L8.unpack requestXml CM.liftM head (runX $ parseHTML requestXMLString //> atId "rid" >>> getAttrValue "value")- + -- | Send query with or without optional arguments and return response HTML-sendQuery :: String -> String -> String -> Maybe String -> IO L8.ByteString-sendQuery program database querySequence optionalArguments- | isJust optionalArguments = simpleHttp ("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?CMD=Put&PROGRAM=" ++ program ++ "&DATABASE=" ++ database ++ fromJust optionalArguments ++ "&QUERY=" ++ querySequence)- | otherwise = simpleHttp ("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?CMD=Put&PROGRAM=" ++ program ++ "&DATABASE=" ++ database ++ "&QUERY=" ++ querySequence)+sendQueryEBI :: String -> String -> String -> Maybe String -> IO L8.ByteString+sendQueryEBI program' database' querySequences' _ = do+ putStrLn "Making HTTP request"+ res <- do+ --initReq <- parseUrl "http://postcatcher.in/catchers/541811052cb53502000001a7"+ initReq <- parseUrl "http://www.ebi.ac.uk/Tools/services/rest/ncbiblast/run"+ let req = (flip urlEncodedBody) initReq $+ [ ("email", "florian.eggenhofer@univie.ac.at")+ , ("program", (B.pack program'))+ , ("database", (B.pack database'))+ , ("stype", "dna")+ , ("sequence", (B.pack querySequences'))+ ]+ withManager $ httpLbs req+ { method = "POST" }+ putStrLn "EBI Response"+ print res+ putStrLn "EBI Response Body"+ print (responseBody res)+ return (responseBody res) ++-- | Send query with or without optional arguments and return response HTML+sendQueryNCBI :: String -> String -> String -> Maybe String -> IO L8.ByteString+sendQueryNCBI program' database' querySequences' optionalArguments'+ | isJust optionalArguments' = simpleHttp ("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?CMD=Put&PROGRAM=" ++ program' ++ "&DATABASE=" ++ database' ++ fromJust optionalArguments' ++ "&QUERY=" ++ querySequences')+ | otherwise = simpleHttp ("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?CMD=Put&PROGRAM=" ++ program' ++ "&DATABASE=" ++ database' ++ "&QUERY=" ++ querySequences') -- | Retrieve session status with RID-retrieveSessionStatus :: String -> IO String -retrieveSessionStatus rid = do- statusXml <- withSocketsDo- $ simpleHttp ("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?CMD=Get&FORMAT_OBJECT=SearchInfo&RID=" ++ rid)- let statusXMLString = L8.unpack statusXml- return statusXMLString+retrieveSessionStatus :: String -> String -> IO String +retrieveSessionStatus provider' rid = do+ if provider' == "ebi"+ then do+ statusXml <- withSocketsDo $ simpleHttp ("http://www.ebi.ac.uk/Tools/services/rest/ncbiblast/status/" ++ rid)+ let statusXMLString = L8.unpack statusXml+ putStrLn "EBI statusXMLString"+ return statusXMLString+ else do+ statusXml <- withSocketsDo $ simpleHttp ("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?CMD=Get&FORMAT_OBJECT=SearchInfo&RID=" ++ rid)+ let statusXMLString = L8.unpack statusXml+ return statusXMLString -- | Retrieve result in blastxml format with RID -retrieveResult :: String -> IO (Either String BlastResult)-retrieveResult rid = do- statusXml <- withSocketsDo- $ simpleHttp ("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?RESULTS_FILE=on&RID=" ++ rid ++ "&FORMAT_TYPE=XML&FORMAT_OBJECT=Alignment&CMD=Get")- resultXML <- parseXML statusXml- return (Right resultXML)-+retrieveResult :: String -> String -> IO (Either String BlastResult)+retrieveResult provider' rid = do+ if provider' == "ebi"+ then do+ statusXml <- withSocketsDo $ simpleHttp ("http://www.ebi.ac.uk/Tools/services/rest/ncbiblast/result/" ++ rid ++ "/xml")+ resultXML <- parseXML statusXml+ return (Right resultXML)+ else do+ statusXml <- withSocketsDo $ simpleHttp ("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?RESULTS_FILE=on&RID=" ++ rid ++ "&FORMAT_TYPE=XML&FORMAT_OBJECT=Alignment&CMD=Get")+ resultXML <- parseXML statusXml+ return (Right resultXML)+ -- | Check if job results are ready and then retrieves results-checkSessionStatus :: String -> IO (Either String BlastResult)-checkSessionStatus rid = do- threadDelay 60000000- status <- retrieveSessionStatus rid- waitOrRetrieve status rid +-- If a walltime in microseconds was set query retrieval will termiate after it is consumed and return a Left result+checkSessionStatus :: String -> String -> Maybe Int -> Int -> IO (Either String BlastResult)+checkSessionStatus provider' rid walltime consumedTime = do+ threadDelay 120000000+ status <- retrieveSessionStatus provider' rid+ if (isNothing walltime)+ then do+ waitOrRetrieve provider' status rid walltime consumedTime+ else do+ if (consumedTime < (fromJust walltime))+ then do + waitOrRetrieve provider' status rid walltime (consumedTime + 120000000)+ else do + let exceptionMessage = "BLASTHTTP: Query did not return result within walltime"+ return (Left exceptionMessage) -waitOrRetrieve :: String -> String -> IO (Either String BlastResult)-waitOrRetrieve status rid - | "Status=READY" `isInfixOf` status = retrieveResult rid+waitOrRetrieve :: String -> String -> String -> Maybe Int -> Int -> IO (Either String BlastResult)+waitOrRetrieve provider' status rid walltime consumedTime+ | provider' == "ebi" = waitOrRetrieveEBI status rid walltime consumedTime+ | otherwise = waitOrRetrieveNCBI status rid walltime consumedTime++waitOrRetrieveEBI :: String -> String -> Maybe Int -> Int -> IO (Either String BlastResult)+waitOrRetrieveEBI status rid walltime consumedTime+ | "FINISHED" `isInfixOf` status = retrieveResult "ebi" rid+ | "FAILURE" `isInfixOf` status = do+ let exceptionMessage = "BLASTHTTP: The EBI blast job failed."+ return (Left exceptionMessage)+ | "ERROR" `isInfixOf` status = do+ let exceptionMessage = "BLASTHTTP: An error occurred attempting to get the EBI blast job status."+ return (Left exceptionMessage)+ | "NOT_FOUND" `isInfixOf` status = do+ let exceptionMessage = "BLASTHTTP: The EBI blast job cannot be found."+ return (Left exceptionMessage)+-- RUNNING+ | otherwise = checkSessionStatus "ebi" rid walltime consumedTime++waitOrRetrieveNCBI :: String -> String -> Maybe Int -> Int -> IO (Either String BlastResult)+waitOrRetrieveNCBI status rid walltime consumedTime+ | "Status=READY" `isInfixOf` status = retrieveResult "ncbi" rid | "Status=FAILURE" `isInfixOf` status = do let exceptionMessage = "Search $rid failed; please report to blast-help at ncbi.nlm.nih.gov.\n" return (Left exceptionMessage) | "Status=UNKNOWN" `isInfixOf` status = do let exceptionMessage = "Search $rid expired.\n" return (Left exceptionMessage)- | otherwise = checkSessionStatus rid+ | "Status=WAITING" `isInfixOf` status = do+ checkSessionStatus "ncbi" rid walltime consumedTime+ --Unexpected status, return Left+ | otherwise = do+ let exceptionMessage = "Status has unexpected value " ++ status ++ " - aborting blast search\n"+ return (Left exceptionMessage) -- | Sends Query and retrieves result on reaching READY status, will return exeption message if no query sequence has been provided -performQuery :: String -> String -> Maybe SeqData -> Maybe String -> IO (Either String BlastResult) -performQuery program database querySequenceMaybe optionalArgumentMaybe- | isJust querySequenceMaybe = do - rid <- startSession program database (L8.unpack (unSD (fromJust querySequenceMaybe))) optionalArgumentMaybe- checkSessionStatus rid- | otherwise = do - let exceptionMessage = "Error - no query sequence provided"- return (Left exceptionMessage)+performQuery :: String -> String -> String -> [Sequence] -> Maybe String -> Maybe Int -> IO (Either String BlastResult) +performQuery provider' program' database' querySequences' optionalArgumentMaybe walltime+ | null querySequences' = do + let exceptionMessage = "Error - no query sequence provided"+ return (Left exceptionMessage)+ | otherwise = do+ let sequenceString = urlEncode (concatMap showSequenceString querySequences')+ rid <- startSession provider' program' database' sequenceString optionalArgumentMaybe+ checkSessionStatus provider' rid walltime (0 :: Int) +showSequenceString :: Sequence -> String+showSequenceString fastaSequence = sequenceString+ where sequenceHeader = ">" ++ L8.unpack (unSL (seqheader fastaSequence)) ++ "\n"+ sequenceData = L8.unpack (unSD (seqdata fastaSequence)) ++ "\n"+ sequenceString = sequenceHeader ++ sequenceData+ -- | Retrieve Blast results in BlastXML format from the NCBI REST Blast interface -- The querySequence has to be provided, all other parameters are optional and can be set to Nothing -- optionalArguments is attached to the query as is .e.g: "&ALIGNMENTS=250" blastHTTP :: BlastHTTPQuery -> IO (Either String BlastResult)-blastHTTP (BlastHTTPQuery program database querySequence optionalArguments) = do+blastHTTP (BlastHTTPQuery provider' program' database' querySequences' optionalArguments' walltime') = do+ let defaultProvider = "ncbi" let defaultProgram = "blastn"- let defaultDatabase = "refseq_genomic" - let selectedProgram = fromMaybe defaultProgram program- let selectedDatabase = fromMaybe defaultDatabase database - performQuery selectedProgram selectedDatabase querySequence optionalArguments-+ let defaultDatabase = "refseq_genomic" + let defaultWalltime = Nothing+ let selectedProvider = fromMaybe defaultProvider provider'+ let selectedProgram = fromMaybe defaultProgram program'+ let selectedDatabase = fromMaybe defaultDatabase database' + let selectedWalltime = maybe defaultWalltime Just walltime'+ --walltime of 1h in microseconds+ --let walltime = Just (7200000000 ::Int)+ performQuery selectedProvider selectedProgram selectedDatabase querySequences' optionalArguments' selectedWalltime -