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]

Flags

Manual Flags

NameDescriptionDefault
example

Build the example too

Disabled

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

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.1.0.1, 0.1.0.2, 0.1.0.3, 0.1.0.4, 0.1.1.0, 0.1.1.1
Dependencies aeson (>=1.3.1.1 && <1.4), base (>=4.7 && <5), blaze-html, bytestring (>=0.10.8.1 && <0.11), charset (>=0.3.7.1 && <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.2.3.0 && <1.3), wai, warp [details]
License BSD-3-Clause
Copyright 2019 Erik Aker
Author Erik Aker
Maintainer eraker@gmail.com
Category Web
Home page https://github.com/erewok/servant-py#readme
Source repo head: git clone https://github.com/erewok/servant-py
Uploaded by erewok at 2019-01-12T16:19:17Z
Distributions
Reverse Dependencies 1 direct, 0 indirect [details]
Executables servant-py-exe
Downloads 3497 total (32 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2019-01-12 [all 1 reports]

Readme for servant-py-0.1.1.1

[back to package description]

servant-py

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

Inspiration

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

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

{-# 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:

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