packages feed

BlastHTTP 1.0.1 → 1.2.0

raw patch · 4 files changed

+184/−66 lines, 4 filesdep +biofasta

Dependencies added: biofasta

Files

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  [![Build Status](https://travis-ci.org/eggzilla/BlastHTTP.svg?branch=master)](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 -