読者です 読者をやめる 読者になる 読者になる

KZKY memo

自分用メモ.

Flask-Restful

基本

flaskのみだと

if request.method == "GET": 
   ...

な感じで,HTTP Methodと振る舞いを対応付けるが,これをやってくれるのが,Flask-RESTful. Resourceクラスを拡張したクラスとURL Routingを書いてそれらを紐付ける.

Basic Sample Code
from flask import Flask, request
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

# Hello World
class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world by get'}

    def post(self):
        return {'hello': 'world by post'}

    def put(self):
        return {'hello': 'world by put'}

    def delete(self):
        return {'hello': 'world by delete'}

api.add_resource(HelloWorld, '/hello')

# call like
"""
curl -X GET "http://localhost:5000/hello"
curl -X POST "http://localhost:5000/hello"
curl -X put "http://localhost:5000/hello"
curl -X DELETE "http://localhost:5000/hello"
"""

# Todo Sample
todos = {}
class TodoSimple(Resource):
    def get(self, todo_id):
        return {todo_id: todos[todo_id]}

    def put(self, todo_id):
        todos[todo_id] = request.form['data']
        return {todo_id: todos[todo_id]}


api.add_resource(TodoSimple, '/<string:todo_id>')

if __name__ == '__main__':
    app.run(debug=True)

# call like
"""
curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT
curl http://localhost:5000/todo1
curl http://localhost:5000/todo2 -d "data=Change my brakepads" -X PUT
curl http://localhost:5000/todo2
"""

メジャー機能

  • クラス内で,HTTP Methodで振り分ける
  • Endpoints
  • パラメータのバリデーション機能
  • リスポンスクラスを定義してそれでレスを返す
  • Blueprintとの連携

Endpoints

Resourceを拡張したクラスとURL routingを1対1で対応付けるが,1対 nでも大丈夫.

api.add_resource(HelloWorld,
    '/',
    '/hello')

Endpointを設定

api.add_resource(Todo,
    '/todo/<int:todo_id>', endpoint='todo_ep')

Argument Parsing (Request Parameter Validation)

参考

どこからパースするか?,答えは,

By default, the RequestParser tries to parse values from flask.Request.values, and flask.Request.json.

なので,form dataと"application/json"のcontent-typeでデータが来た時にそのデータをパースすると思っていい.

  • どこ(query, form, header, cookie, file, etc)から読みだすかは変更可能
  • Common Argsに関するパーサーは使い回したいから継承も可能
  • 値がlistの場合はaction="append"とすること
Argument Parsing Sample Coce
from flask import Flask
from flask_restful import Resource, Api, reqparse

app = Flask(__name__)
api = Api(app)

# Hello World
class HelloWorld(Resource):

    parser = reqparse.RequestParser()
    parser.add_argument('rate', type=int, help='Rate cannot be converted')
    parser.add_argument('name', required=True)
    parser.add_argument('name_relocated', dest="name_dest")
    parser.add_argument('todos', type=str, action="append")
        
    def post(self):
        args = self.parser.parse_args()

        print args
        return args

api.add_resource(HelloWorld, '/hello')

if __name__ == '__main__':
    app.run(debug=True)

Output Fields

参考

普通にデータ dict で返せばいいが,RESをクラスで定義して,返したいときもある.そういう時に, "fields" module and "the marshal_with()" decoratorの機能がある.Django ORM and WTFormに似ているそう.

resource_fields (=dict object)とレスポンスクラスを作って,コントローラークラス(Resourceを継承したクラス)のなかで,

resource_fields = {
    ...
}

@marshal_with(resource_fields)
def get(self, ...): 
    ...

な感じで使用する

  • 独自フィールドも作成可能
  • ListやNestedフィールドも作成可能
Ouptut Fields Sample Code
from flask_restful import fields, marshal_with
from flask import Flask
from flask_restful import Resource, Api
from flask_restful import fields, marshal_with

# Restful Basics
app = Flask(__name__)
api = Api(app)

# fields module and marshal_with decorator
user_fields = {
    'id': fields.Integer,
    'name': fields.String,
}

resource_fields = {
    'task_id': fields.Integer,
    'task': fields.String(default="Default task"),
    'tasks': fields.List(fields.String(default="Default task")),
    'users': fields.List(fields.Nested(user_fields)),
    #'uri': fields.Url('todo_ep')
}

class TodoDao(object):
    def __init__(self, task_id, task, tasks, users):
        self.task_id = task_id
        self.task = task
        self.tasks = tasks
        self.users = users

        # This field will not be sent in the response (meaning can have other state)
        self.status = 'active'

class Todo(Resource):
    @marshal_with(resource_fields)
    def get(self, task_id):

        task_id = task_id
        task = 'Remember the milk'
        tasks = ["Remember the milk 000", "Remember the milk 001", "Remember the milk 002'"]

        users = [
            {"id": 0, "name": "kzky000"},
            {"id": 1, "name": "kzky001"},
            {"id": 2, "name": "kzky002"}
        ]
        return TodoDao(task_id=task_id, task=task, tasks=tasks, users=users)

api.add_resource(Todo, '/todos/<int:task_id>')

if __name__ == '__main__':
    app.run(debug=True)

# call like
"""
curl -X GET "http://localhost:5000/todos/10"
"""

Use with Blueprint

当然Blueprintとの連携も可能

With Blurpint Sample Code
from flask import Flask, Blueprint
from flask_restful import Api, Resource

app = Flask(__name__)
api_bp = Blueprint('api', "flask-restful with blueprint", url_prefix="/api",)
api = Api(api_bp)

class TodoItem(Resource):
    def get(self, id):
        return {'task': 'Say "Hello, World!" for {}'.format(id)}

api.add_resource(TodoItem, '/todos/<int:id>')
app.register_blueprint(api_bp)

if __name__ == '__main__':
    app.run(debug=True)

# call like
"""
curl -X GET "http://localhost:5000/api/todos/10"
"""

Sample Codes

すべてここにあるflask_restful_のprefixのやつ

設計方針

1つのファイルに

  • reqeust parser
  • fields
  • response class
  • resoruce class

を書けばいいと思う.

  • reqeust parser
  • fields

は,resoruce classの中にclass memberとして書いたほうがいいかも