# servant-py [![Build Status](https://dev.azure.com/eraker62/servant-py/_apis/build/status/erewok.servant-py?branchName=master)](https://dev.azure.com/eraker62/servant-py/_build/latest?definitionId=1?branchName=master) This library lets you derive automatically Python functions that let you query each endpoint of a *servant* webservice. Currently, the only supported method for generating requests is via the `requests` library, which is the recommended way to generate HTTP requests in the Python world (even among Python core devs). ## Inspiration This library is largely inspired by [servant-js](https://github.com/haskell-servant/servant-js) and by the fantastic work of the Servant team in general. Any good ideas you find in here are from their work (any mistakes are almost entirely mine, however). ## Example There are two different styles of function-return supported here: `DangerMode` and `RawResponse`. The latter returns the raw response from issuing the request and the former calls `raise_for_status` and then attempts to return `resp.json()`. You can switch which style you'd like to use by creating a proper `CommonGeneratorOptions` object. The default options just chucks it all to the wind and goes for `DangerMode` (because, seriously, we're using Haskell to generate Python here...). Following is an example of using the Servant DSL to describe endpoints and then using `servant-py` to create Python clients for those endpoints. #### Servant DSL API Description ``` haskell {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TypeOperators #-} module Main where import Data.Aeson import qualified Data.ByteString.Char8 as B import Data.Proxy import qualified Data.Text as T import GHC.Generics import Servant import System.FilePath import Servant.PY -- * A simple Counter data type newtype Counter = Counter { value :: Int } deriving (Generic, Show, Num) instance ToJSON Counter data LoginForm = LoginForm { username :: !T.Text , password :: !T.Text , otherMissing :: Maybe T.Text } deriving (Eq, Show, Generic) instance ToJSON LoginForm -- * Our API type type TestApi = "counter-req-header" :> Post '[JSON] Counter :<|> "counter-queryparam" :> QueryParam "sortby" T.Text :> Header "Some-Header" T.Text :> Get '[JSON] Counter :<|> "login-queryflag" :> QueryFlag "published" :> Get '[JSON] LoginForm :<|> "login-params-authors-with-reqBody" :> QueryParams "authors" T.Text :> ReqBody '[JSON] LoginForm :> Post '[JSON] LoginForm :<|> "login-with-path-var-and-header" :> Capture "id" Int :> Capture "Name" T.Text :> Capture "hungrig" Bool :> ReqBody '[JSON] LoginForm :> Post '[JSON] (Headers '[Header "test-head" B.ByteString] LoginForm) testApi :: Proxy TestApi testApi = Proxy -- where our static files reside result :: FilePath result = "examples" main :: IO () main = writePythonForAPI testApi requests (result "api.py") ``` #### Generated Python Code If you build the above and run it, you will get some output that looks like the following: ```python from urllib import parse import requests def post_counterreqheader(): """ POST "counter-req-header" """ url = "http://localhost:8000/counter-req-header" resp = requests.post(url) resp.raise_for_status() return resp.json() def get_counterqueryparam(sortby, headerSomeHeader): """ GET "counter-queryparam" """ url = "http://localhost:8000/counter-queryparam" headers = {"Some-Header": headerSomeHeader} params = {"sortby": sortby} resp = requests.get(url, headers=headers, params=params) resp.raise_for_status() return resp.json() def get_loginqueryflag(published): """ GET "login-queryflag" """ url = "http://localhost:8000/login-queryflag" params = {"published": published} resp = requests.get(url, params=params) resp.raise_for_status() return resp.json() def post_loginparamsauthorswithreqBody(authors, data): """ POST "login-params-authors-with-reqBody" """ url = "http://localhost:8000/login-params-authors-with-reqBody" params = {"authors": authors} resp = requests.post(url, params=params, json=data) resp.raise_for_status() return resp.json() def post_loginwithpathvarandheader_by_id_by_Name_by_hungrig(id, Name, hungrig, data): """ POST "login-with-path-var-and-header/{id}/{Name}/{hungrig}" Args: id Name hungrig """ url = "http://localhost:8000/login-with-path-var-and-header/{id}/{Name}/{hungrig}".format( id=parse.quote(id), Name=parse.quote(Name), hungrig=parse.quote(hungrig)) resp = requests.post(url, json=data) resp.raise_for_status() return resp.json() ``` If you would like to compile and run this example yourself, you can do that like so: ``` $ stack build --flag servant-py:example $ stack exec servant-py-exe $ cat examples/api.py ```