Flask-MarshmallowとFlask-Restlessという拡張機能を使い、前回作成したデータベースモデルをWeb API化する。
連載「Visual Studio Codeで始めるPythonプログラミング」
前回は、Flask-SQLAlchemyを利用して、ToDoリストのデータをデータベースに保存するコードを書いた。今回は、ToDoリストの取得/追加などを行うAPIを作成してみよう。
まずは、前回のコードを素直にWeb API化してみる。次に、Flask用のWeb API拡張機能である「Flask-Restless」を使用して、お手軽なWeb API化を試し、最後に後者を呼び出してToDoリストの一覧と追加を行うフロントエンドを作成してみよう(ここで作成する2つのWeb APIが返送するJSONデータの仕様を一緒にすればよかったのだが、ちゃんと設計しなかったため、互換性がない)。
なお、本稿ではVisual Studio Code(以下、VS Code)のWindows版バージョン1.26.1と同じくWindows版のPython 3.6.5を利用している。
ToDoリストAPIの基本方針としては、次のようになる。
GET/POST/DELETE/PUTにより、データベースに対するToDoリスト項目の取得/作成/削除/状態変更を行う。例えば、http://localhost:5000/api/todoitemsに対してGETリクエストを送信すると、次のようなJSONデータを取得するといった具合だ(以下の最初の例は最初に作成するWeb APIが返送するデータ例、次の画像例は後者が返送するデータ例)。
[
{"done":false,"item_id":1,"title":"foo"},
{"done":false,"item_id":2,"title":"bar"},
{"done":false,"item_id":3,"title":"foo"}
]
前回に使用したFlaskとFlask-SQLAlchemyの組み合わせは、データベース操作を簡単にしてくれるものだったが、Web APIとしてJSONデータをクライアントに送信するには、さらに別の拡張機能を使った方が簡単になる。
最初の例では、「Flask-Marshmallow」拡張機能を使用して、データベースに保存されたデータのシリアライズ/デシリアライズを行う。2つ目の例では、データベースのWeb API化をより簡単に行える「Flask-Restless」拡張機能を使ってみよう。
この方法でWeb API化するプロジェクトでは、「pip install flask flask-sqlalchemy flask-marshmallow」コマンドを実行してFlaskとそこで利用する拡張機能をインストールした仮想環境を作成している。
Flask-Marshmallowは、オブジェクトのシリアライズ/デシリアライズを行う「marshmallow」のFlask用ラッパーだ。大ざっぱには次のような手順でJSON化されたデータをクライアントに返送する。
前回は、todo.pyファイルでデータベースモデルとなるToDoItemクラスと、それを操作するインタフェースとしてToDoListクラスを定義していたが、今回はソースコードがシンプルになるように、ToDoListクラスで行っていた操作をapp.pyのビュー関数に移行している。全体としてのプロジェクト構成は次のようになる。
そのため、上の手順の1.の内容がtodo.pyファイルに、2.の内容がapp.pyファイルにまとまるようになっている。以下ではそれらを順番に見ていこう。
データベース操作をまとめたtodo.pyファイルの内容は次のようになっている。
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
db = SQLAlchemy()
ma = Marshmallow() # Flask-Marshmallowの利用設定
def init_db(app):
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db/todoitems.db"
db.init_app(app)
return db
def init_schema(app): # Flask-Marshmallowオブジェクトをappオブジェクトで初期化
ma.init_app(app)
class ToDoItem(db.Model):
__tablename__ = "todoitems"
item_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
done = db.Column(db.Boolean, nullable=False, default=False)
class ItemSchema(ma.Schema):
class Meta:
fields = ("item_id", "title", "done")
item_schema = ItemSchema()
items_schema = ItemSchema(many=True)
Flask-MarshmallowパッケージからMarshmallowクラスをインポートして、そのインスタンスを作成し、appオブジェクトでその初期化を行うという流れは前回のFlask-SQLAlchemyの利用パターンと同じだ(強調書体の部分)。
ToDoItemクラスは前回も見た通りのものだ。そして、ItemSchemaクラスでは、ToDoItemクラスが持つ3つのフィールドを指定して、出力形式を指定している(「fields = ("item_id", "title", "done")」の部分)。
item_schema/items_schemaはこのスキーマ定義クラスのインスタンスであり、それぞれ単一データ/複数データをJSON化する際に使用する。例えば、ItemSchemaクラスにはjsonifyメソッドがあるので、データベースから取得したデータを「item_schema.jsonify(somedata)」のようにしてJSON化できる。後は、そのデータをビュー関数でクライアントに返送すればよい。
リクエストを受け取り、データベースにアクセスし、クライアントに結果を返送する関数群は前回までと同様にapp.pyファイルに記述した。コードを以下に示す。
from flask import Flask, jsonify, request
from todo import ToDoItem, init_db, init_schema
from todo import item_schema, items_schema
app = Flask(__name__)
db = init_db(app)
init_schema(app)
@app.route("/api/todoitems", methods=["GET"])
def get_todoitems():
items = ToDoItem.query.all()
return items_schema.jsonify(items)
@app.route("/api/todoitems/<int:item_id>", methods=["GET"])
def get_todoitem(item_id):
item = ToDoItem.query.filter_by(item_id=item_id).first_or_404()
return item_schema.jsonify(item)
@app.route("/api/todoitems", methods=["POST"])
def add_todoitem():
if not "title" in request.json:
return "error"
item = ToDoItem(title=request.json["title"], done=False)
db.session.add(item)
db.session.commit()
return item_schema.jsonify(item)
@app.route("/api/todoitems/<int:item_id>", methods=["DELETE"])
def delete_todoitem(item_id):
item = ToDoItem.query.filter_by(item_id=item_id).first_or_404()
db.session.delete(item)
db.session.commit()
return jsonify({"result": True})
@app.route("/api/todoitems/<int:item_id>", methods=["PUT"])
def update_todoitem(item_id):
item = ToDoItem.query.filter_by(item_id=item_id).first_or_404()
item.done = not item.done
db.session.commit()
return item_schema.jsonify(item)
Flaskアプリのインスタンスを作成して、それをinit_db/init_schema関数に渡して、SQLAlchemy/Marshmallowオブジェクトの初期化を行った後は、クライアントからのリクエストを処理するコードが続いている。各関数は典型的なFlaskアプリのビュー関数となっているが、全てのビュー関数の@app.routeデコレータには受け付けるHTTPメソッドが明記されていることに注意されたい。幾つかの関数を例に説明をしておこう。
例えば、全項目の取得を行うget_todosメソッドは次のようになっている。
@app.route("/api/todoitems", methods=["GET"])
def get_todoitems():
items = ToDoItem.query.all()
return items_schema.jsonify(items)
@app.routeデコレータでは、リクエストのURLとHTTPメソッドが本稿冒頭で述べたように指定されている。また、クライアントに返送されるデータはitems_schema.jsonifyメソッドでJSON化しているが、ここでは複数のToDo項目を返送するので「ItemSchema(many=True)」としてインスタンス化したitems_schemaオブジェクトを使用している。
指定したitem_idの項目を取り出すget_todoitem関数は次のようになっている。
@app.route("/api/todoitems/<int:item_id>", methods=["GET"])
def get_todoitem(item_id):
item = ToDoItem.query.filter_by(item_id=item_id).first_or_404()
return item_schema.jsonify(item)
ここで注目したいのは「first_or_404メソッド」だ。これはデータベースにアクセスして、指定した条件に合致する項目がなかったときには以降の処理を中断して「404 Not Found」を返してくれる。また、ここでは取得する項目は1つだけなので、item_schemaオブジェクトを使ってJSON化を行っている。
新規項目を登録するregister_todoitem関数は次のようになっている。
@app.route("/api/todoitems", methods=["POST"])
def add_todoitem():
if not "title" in request.json:
return "error"
item = ToDoItem(title=request.json["title"], done=False)
db.session.add(item)
db.session.commit()
return item_schema.jsonify(item)
基本構造はget_todoitems関数と同様だが、こちらは受け取ったデータ(request.jsonオブジェクト)に、"title"をキーとする要素が含まれているかをチェックして、それがある場合にだけ新規にデータを作成するようにしている。
といったところが、app.pyファイルで重要な部分だ。最後にこのWeb APIの動作を確認しておこう。
前回と同様の手順(環境変数FLASK_APPの値をapp.pyに設定して、「flask shell」コマンドを実行しREPL環境を起動した後に、「from todo import db」と「db.create_all()」を実行)で、データベースを作成したら、「flask run」コマンドを実行してアプリを起動し、curlコマンドでその動作を確認してみよう。
例えば、新規に項目を作成するなら次のようになる。
> curl -H "Content-Type: application/json" -X POST -d "{\"title\": \"play game\"}" http://localhost:5000/api/todoitems
また、全項目の取得なら次のようになる。
> curl -H "Content-type: application/json" -X GET http://localhost:5000/api/todoitems
以下はPOSTリクエストで幾つかの項目を作成して、最後にGETリクエストで全項目を取得しているところだ。
ここでは、VS Codeのコマンドパレットから[Python: Create Terminal]コマンドを実行して、仮想環境が有効化されたターミナルを開き、そこで「flask run」コマンドを実行しアプリを起動した後に、[ターミナルの分割]ボタンをクリックして、1つのターミナルウィンドウにアプリからのメッセージとコマンドプロンプトを表示するようにしている。これにより、右側のコマンドプロンプトでcurlコマンドを実行すると、Web APIがどう反応しているかを左側のペーンで確認できる。
ここまで、Flask-Marshmallowを利用してWeb APIを作成する方法を見てきた。次ページでは、これをより簡単に行ってくれるFlask-Restlessを使ってWeb APIを作成してみよう。
Copyright© Digital Advantage Corp. All Rights Reserved.