生成したmarshmallowのschemaをwrapしてpyramidから使えるようにしてみた

swaggerからmarshmallowのschemaを生成する機能は昔から作っていて、デフォルトでは definitions 部分だけしか見ないのだけれど。--full というオプションをつけると paths 以下の parametersresponses も見るようになっている。ここで生成したschemaをpyramidから使えるようにした。まだpypiにはあげていない。

だいたい以下の様な手順で使う。

$ pip install swagger-marshmallow-codegen
$ swagger-marshmallow-codegen swagger.yml --full > schema.py

あとは以下の様な感じでコードを書けばOK。以下が重要。

  • config.include("toybox.swagger")
  • withswaggerで生成されたschemaを渡したdecoratorをつける
from datetime import datetime, timedelta
from toybox.simpleapi import simple_view, run
from toybox.swagger import withswagger
import schema  # ./schema.py


@simple_view("/", decorator=withswagger(input=schema.Input, output=schema.Output))
def hello(request):
    return {'message': 'Welcome, {}!'.format(request.GET["name"])}


@simple_view("/add", request_method="POST", decorator=withswagger(input=schema.AddInput, output=schema.AddOutput))
def add(request):
    x = request.json["x"]
    y = request.json["y"]
    return {"result": x + y}


@simple_view("/dateadd", request_method="POST", decorator=withswagger(input=schema.DateaddInput, output=schema.DateaddOutput))
def dateadd(request):
    value = request.json["value"]
    addend = request.json["addend"]
    unit = request.json["unit"]
    value = value or datetime.utcnow()
    if unit == 'minutes':
        delta = timedelta(minutes=addend)
    else:
        delta = timedelta(days=addend)
    result = value + delta
    return {'result': result}


if __name__ == "__main__":
    import logging
    logging.basicConfig(level=logging.DEBUG)
    run.include("toybox.swagger")
    run(port=5001)

viewの内部では全てswaggerのspecを満たしたrequsestであることが保証されている。ダメだった場合にはHTTPBadRequestが返る。

例えば以下の様な感じ

$ http POST :5001/add x=10 y=20
HTTP/1.0 200 OK
Content-Length: 14
Content-Type: application/json
Date: Sat, 18 Mar 2017 18:24:52 GMT
Server: WSGIServer/0.2 CPython/3.5.2

{
    "result": 30
}

$ http POST :5001/add x=10
HTTP/1.0 400 Bad Request
Content-Length: 107
Content-Type: application/json
Date: Sat, 18 Mar 2017 18:24:55 GMT
Server: WSGIServer/0.2 CPython/3.5.2

{
    "code": "400 Bad Request",
    "message": {
        "y": [
            "Missing data for required field."
        ]
    },
    "title": "Bad Request"
}

$ http POST :5001/add
HTTP/1.0 400 Bad Request
Content-Length: 214
Content-Type: application/json
Date: Sat, 18 Mar 2017 18:24:57 GMT
Server: WSGIServer/0.2 CPython/3.5.2

{
    "code": "400 Bad Request",
    "message": "The server could not comply with the request since it is either malformed or otherwise incorrect.\n\n\nExpecting value: line 1 column 1 (char 0)\n\n",
    "title": "Bad Request"
}

viewへのinputとして渡せるのはswaggerと同様に以下。

  • path – /foo/{name} みたいなやつ
  • query – /?foo=bar みたいなやつ
  • header – request header
  • body – REST APIなどでjsonをpostしたときのやつ
  • form – 普通のPOST

example全体は以下のリンク先。

https://github.com/podhmo/toybox/tree/master/examples/swagger

どうでも良いこと

ところでmarshmallowのvalidationがload(deserialization)時にだけ掛かるということに気づき衝撃を受けた。しょうがないのでserialize後deserializeするみたいなコードになっている。