servant-py: Automatically derive python functions to query servant webservices.

[ bsd3, library, web ] [ Propose Tags ]

Automatically derive python functions to query servant webservices.

Supports deriving functions using Python's requests library.

[Skip to Readme]
Versions [faq],,,,,
Dependencies aeson (>= && <1.4), base (>=4.7 && <5), blaze-html, bytestring (>= && <0.11), charset (>= && <0.4), filepath, lens (>=4.16.1 && <5), servant (>=0.11 && <0.16), servant-blaze, servant-foreign (>=0.11 && <0.16), servant-py, servant-server, stm, text (>= && <1.3), wai, warp [details]
License BSD-3-Clause
Copyright 2019 Erik Aker
Author Erik Aker
Category Web
Home page
Source repo head: git clone
Uploaded by erewok at Sat Jan 12 16:19:17 UTC 2019
Distributions NixOS:
Executables servant-py-exe
Downloads 1090 total (91 in the last 30 days)
Rating (no votes yet) [estimated by rule of succession]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs available [build log]
Last success reported on 2019-01-12 [all 1 reports]


[Index] [Quick Jump]



Build the example too


Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info


Maintainer's Corner

For package maintainers and hackage trustees

Readme for servant-py-

[back to package description]


Build Status

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).


This library is largely inspired by 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).


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

{-# 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 </> "")

Generated Python Code

If you build the above and run it, you will get some output that looks like the following:

from urllib import parse

import requests

def post_counterreqheader():
    POST "counter-req-header"

    url = "http://localhost:8000/counter-req-header"

    resp =
    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,

    return resp.json()

def get_loginqueryflag(published):
    GET "login-queryflag"

    url = "http://localhost:8000/login-queryflag"

    params = {"published": published}
    resp = requests.get(url,

    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 =,

    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}"
    url = "http://localhost:8000/login-with-path-var-and-header/{id}/{Name}/{hungrig}".format(

    resp =,

    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/