datarobot (empty) → 0.1.0
raw patch · 10 files changed
+422/−0 lines, 10 filesdep +aesondep +basedep +bytestringsetup-changed
Dependencies added: aeson, base, bytestring, datarobot, exceptions, microlens, network-uri, safe, scientific, string-conversions, text, unordered-containers, vector, wreq
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- datarobot.cabal +72/−0
- src/DataRobot.hs +42/−0
- src/DataRobot/API.hs +29/−0
- src/DataRobot/Features.hs +51/−0
- src/DataRobot/Predict.hs +63/−0
- src/DataRobot/PredictResponse.hs +94/−0
- src/DataRobot/Types.hs +37/−0
- test/Spec.hs +2/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Sean Hess (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Sean Hess nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ datarobot.cabal view
@@ -0,0 +1,72 @@+-- This file has been generated from package.yaml by hpack version 0.17.0.+--+-- see: https://github.com/sol/hpack++name: datarobot+version: 0.1.0+synopsis: Client for DataRobot API+description: Client for DataRobot API+homepage: https://github.com/orbital/datarobot#readme+bug-reports: https://github.com/orbital/datarobot/issues+license: BSD3+license-file: LICENSE+author: Sean Hess+maintainer: seanhess@gmail.com+copyright: Orbital Labs+category: Web+build-type: Simple+cabal-version: >= 1.10++source-repository head+ type: git+ location: https://github.com/orbital/datarobot++library+ hs-source-dirs:+ src+ exposed-modules:+ DataRobot+ DataRobot.API+ DataRobot.Features+ DataRobot.Predict+ DataRobot.PredictResponse+ DataRobot.Types+ build-depends:+ base >=4.7 && <5+ , aeson+ , bytestring+ , exceptions+ , microlens+ , network-uri+ , safe+ , scientific+ , string-conversions+ , text+ , unordered-containers+ , vector+ , wreq+ default-language: Haskell2010++test-suite test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , aeson+ , bytestring+ , exceptions+ , microlens+ , network-uri+ , safe+ , scientific+ , string-conversions+ , text+ , unordered-containers+ , vector+ , wreq+ , base+ , datarobot+ default-language: Haskell2010
+ src/DataRobot.hs view
@@ -0,0 +1,42 @@+module DataRobot+ (+ -- * Types+ Credentials(..)+ , defaultBaseURL+ , defaultBaseURLPredict+ , ProjectID(..)+ , ModelID(..)++ -- * Features API+ , features+ , Features(..)++ -- * Predict API+ , predict+ , Fields+ , PredictError(..)+ , PredictResult(..)+ , responseResult+ , classProbability++++ ) where++import DataRobot.Features+import DataRobot.Predict+import DataRobot.PredictResponse+import DataRobot.Types+import Network.URI (URI, parseURI)+++defaultBaseURL :: URI+defaultBaseURL =+ let (Just u) = parseURI "https://app.datarobot.com/api/v2/"+ in u+++defaultBaseURLPredict :: URI+defaultBaseURLPredict =+ let (Just u) = parseURI "https://simplefinance.orm.datarobot.com/api/v1/"+ in u
+ src/DataRobot/API.hs view
@@ -0,0 +1,29 @@+module DataRobot.API where++import Lens.Micro ((^.))+import Data.ByteString.Lazy (ByteString)+import Data.Aeson (FromJSON, eitherDecode)+import Data.List (intercalate)+import Data.Typeable (Typeable)+import Data.Monoid ((<>))+import Control.Monad.Catch (MonadThrow, Exception, throwM)+import Network.URI (URI(..), pathSegments, uriToString)+import Network.Wreq (Response, responseBody)++data JSONError = JSONError String ByteString+ deriving (Typeable, Show)+instance Exception JSONError+++parseResponse :: (FromJSON a, MonadThrow m) => Response ByteString -> m a+parseResponse r =+ case eitherDecode $ r ^. responseBody of+ Left err -> throwM $ JSONError err (r ^. responseBody)+ Right p -> pure p+++endpoint :: URI -> [String] -> String+endpoint base ps =+ let ps' = "" : pathSegments base <> ps+ u = base { uriPath = intercalate "/" ps' }+ in uriToString id u ""
+ src/DataRobot/Features.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+module DataRobot.Features where++import Lens.Micro ((.~))+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Catch (MonadThrow)+import Data.Monoid ((<>))+import Data.ByteString (ByteString)+import Data.Function ((&))+import Data.String.Conversions (cs)++import DataRobot.Types+import DataRobot.API (parseResponse, endpoint)++import Network.URI (URI(..))+import qualified Network.Wreq as Wreq+import Network.Wreq (defaults, header, checkResponse)+import Network.Wreq.Types (ResponseChecker)++++-- GET https://app.datarobot.com/api/v2/projects/5988c39bc808917519a2acbb/models/5988d164c8089128924bd6cf/features+features :: (MonadIO m, MonadThrow m) => Credentials -> ProjectID -> ModelID -> m Features+features c pid mid = do+ let opts = httpOptions c+ url = featuresEndpoint (baseURL c) pid mid+ r <- liftIO $ Wreq.getWith opts url+ f <- parseResponse r+ pure f+++featuresEndpoint :: URI -> ProjectID -> ModelID -> String+featuresEndpoint base (ProjectID pid) (ModelID mid) =+ endpoint base ["projects", cs pid, "models", cs mid, "features"]+++++httpOptions :: Credentials -> Wreq.Options+httpOptions c =+ defaults+ & header "Authorization" .~ [authorization]+ & header "datarobot-key" .~ [apiKey c]+ & checkResponse .~ Just ignoreStatus+ where+ ignoreStatus :: ResponseChecker+ ignoreStatus _ _ = pure ()++ authorization :: ByteString+ authorization = "Token " <> (apiToken c)
+ src/DataRobot/Predict.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+module DataRobot.Predict+ ( predict+ , predictURI+ , Credentials(..)+ , ProjectID(..)+ , ModelID(..)+ ) where++import Lens.Micro ((?~), (.~))+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Catch (MonadThrow)+import Data.Aeson (Value(..))+import Data.Function ((&))+import qualified Data.HashMap.Strict as HM+import qualified Data.Vector as V+import Data.String.Conversions (cs)++import DataRobot.Types+import DataRobot.API (parseResponse, endpoint)+import DataRobot.PredictResponse++import Network.URI (URI(..))+import qualified Network.Wreq as Wreq+import Network.Wreq (defaults, Auth, basicAuth, auth, header, checkResponse)+import Network.Wreq.Types (ResponseChecker)+++-- https://app.datarobot.com/docs/users-guide/basics/predictions/prediction-api.html+predict :: (MonadIO m, MonadThrow m) => Credentials -> ProjectID -> ModelID -> Fields -> m (Either PredictError PredictResult)+predict c pid mid o = do+ let opts = httpOptions c+ url = predictURI (baseURLPredict c) pid mid+ body = Array $ V.singleton $ Object $ HM.fromList o+ r <- liftIO $ Wreq.postWith opts url body+ pr <- parseResponse r+ pure $ responseResult pr+++httpOptions :: Credentials -> Wreq.Options+httpOptions c =+ defaults+ & auth ?~ (authorization c)+ & header "datarobot-key" .~ [apiKey c]+ & checkResponse .~ Just ignoreStatus+ where+ ignoreStatus :: ResponseChecker+ ignoreStatus _ _ = pure ()++++predictURI :: URI -> ProjectID -> ModelID -> String+predictURI base (ProjectID pid) (ModelID mid) =+ endpoint base [cs pid, cs mid, "predict"]++++authorization :: Credentials -> Auth+authorization c =+ basicAuth (username c) (apiToken c)++
+ src/DataRobot/PredictResponse.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DuplicateRecordFields #-}+module DataRobot.PredictResponse+ ( PredictError(..)+ , PredictResult(..)+ , responseResult+ , classProbability+ ) where++import Control.Applicative ((<|>))+import Control.Monad.Catch (Exception)+import Data.Aeson (FromJSON(..), ToJSON, Value)+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HM+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Safe (headMay)+++data PredictError+ = APIError Code Text+ | MissingPrediction+ deriving (Typeable, Show)++instance Exception PredictError+++newtype PredictResponse = PredictResponse (Either PredictFailure PredictSuccess )+ deriving (Show, Eq)++instance FromJSON PredictResponse where+ parseJSON v = PredictResponse <$>+ ((Right <$> parseJSON v) <|> (Left <$> parseJSON v))++type Code = Int++data PredictFailure = PredictFailure+ { code :: Code+ , status :: Text+ } deriving (Show, Eq, Generic)++instance FromJSON PredictFailure++data PredictSuccess = PredictSuccess+ { predictions :: [Prediction]+ , execution_time :: Float+ , model_id :: Text+ -- , task :: Text+ } deriving (Show, Eq, Generic)++instance FromJSON PredictSuccess++data Prediction = Prediction+ { prediction :: Value+ , class_probabilities :: Maybe (HashMap Text Float)+ } deriving (Show, Eq, Generic)++instance FromJSON Prediction++++-- | Result from the prediction++data PredictResult = PredictResult+ { prediction :: Value+ , predictionTimeMs :: Float+ , modelId :: Text+ , classProbabilities :: Maybe (HashMap Text Float)+ } deriving (Show, Eq, Generic)++instance ToJSON PredictResult++responseResult :: PredictResponse -> Either PredictError PredictResult+responseResult (PredictResponse (Right ps)) =+ fromMaybe (Left MissingPrediction) $ do+ p <- headMay (predictions ps)+ return $ Right $ PredictResult+ { prediction = prediction (p :: Prediction)+ , predictionTimeMs = execution_time ps+ , modelId = model_id ps+ , classProbabilities = class_probabilities p+ }+responseResult (PredictResponse (Left pf)) =+ Left $ APIError (code pf) (status pf)+++classProbability :: Text -> PredictResult -> Maybe Float+classProbability c r = do+ cps <- classProbabilities r+ HM.lookup c cps+
+ src/DataRobot/Types.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DeriveGeneric #-}+module DataRobot.Types where+++import Data.ByteString (ByteString)+import Data.Aeson (ToJSON(..), FromJSON(..), Value)+import Data.Text (Text)+import GHC.Generics (Generic)+import Network.URI (URI(..))++data Credentials = Credentials+ { apiToken :: ByteString+ , apiKey :: ByteString+ , username :: ByteString+ , baseURLPredict :: URI+ , baseURL :: URI+ } deriving (Show, Eq)+++newtype ProjectID = ProjectID Text+ deriving (Show, Eq, ToJSON, FromJSON)+++newtype ModelID = ModelID Text+ deriving (Show, Eq, ToJSON, FromJSON)+++data Features = Features+ { featureNames :: [Text]+ } deriving (Show, Eq, Generic)+instance ToJSON Features+instance FromJSON Features+++type Fields = [(Text, Value)]+
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"