An open API service indexing awesome lists of open source software.

https://github.com/erewok/servant-py

Servant client generators for the Python language
https://github.com/erewok/servant-py

haskell haskell-library python servant servant-client

Last synced: 4 months ago
JSON representation

Servant client generators for the Python language

Awesome Lists containing this project

README

          

# 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
```