連載
» 2018年08月21日 05時00分 公開

Visual Studio Codeで始めるPythonプログラミング:VS CodeとPythonとFlaskでお手軽Web API開発 (1/2)

Flask-MarshmallowとFlask-Restlessという拡張機能を使い、前回作成したデータベースモデルをWeb API化する。

[かわさきしんじ,Insider.NET編集部]
「Visual Studio Codeで始めるPythonプログラミング」のインデックス

連載「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 /api/todoitems:ToDoリスト項目の取得
  • GET /api/todoitems/<int:item_id>:指定項目の取得
  • POST /api/todoitems:新規項目の作成
  • DELETE /api/todoitems/<int:item_id>:指定項目の削除
  • PUT /api/todoitems/<int:item_id>:指定項目のdone状態の反転

 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"}
]


ToDoリストAPIが返送するデータの例1

ToDoリストAPIが返送するデータの例2 ToDoリストAPIが返送するデータの例2

 前回に使用したFlaskとFlask-SQLAlchemyの組み合わせは、データベース操作を簡単にしてくれるものだったが、Web APIとしてJSONデータをクライアントに送信するには、さらに別の拡張機能を使った方が簡単になる。

 最初の例では、「Flask-Marshmallow」拡張機能を使用して、データベースに保存されたデータのシリアライズ/デシリアライズを行う。2つ目の例では、データベースのWeb API化をより簡単に行える「Flask-Restless」拡張機能を使ってみよう。

Flask-Marshmallowを利用したWeb APIの作成

 この方法でWeb API化するプロジェクトでは、「pip install flask flask-sqlalchemy flask-marshmallow」コマンドを実行してFlaskとそこで利用する拡張機能をインストールした仮想環境を作成している。

 Flask-Marshmallowは、オブジェクトのシリアライズ/デシリアライズを行う「marshmallow」のFlask用ラッパーだ。大ざっぱには次のような手順でJSON化されたデータをクライアントに返送する。

  1. Flask-SQLAlchemyを用いて作成したデータベースモデルを基にその出力形式を定義するクラスを作成
  2. ビュー関数からデータベースにアクセスして、その結果を1.で作成したクラスを利用してJSONデータ化して、クライアントに返送

 前回は、todo.pyファイルでデータベースモデルとなるToDoItemクラスと、それを操作するインタフェースとしてToDoListクラスを定義していたが、今回はソースコードがシンプルになるように、ToDoListクラスで行っていた操作をapp.pyのビュー関数に移行している。全体としてのプロジェクト構成は次のようになる。

Flask-Marshmallowを利用したプロジェクトの構成 Flask-Marshmallowを利用したプロジェクトの構成

 そのため、上の手順の1.の内容がtodo.pyファイルに、2.の内容がapp.pyファイルにまとまるようになっている。以下ではそれらを順番に見ていこう。

1. データベースモデルから出力スキーマ情報を持つクラスを定義

 データベース操作をまとめた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を利用して、データベースの出力形式を指定(todo.pyファイル)

 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化できる。後は、そのデータをビュー関数でクライアントに返送すればよい。

データベースから取得したデータを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)

クライアントからのリクエストを受け取る関数群(app.pyファイル)

 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)

全項目を取得するget_todoitems関数

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

指定したitem_idの項目を取り出すget_todoitem関数

 ここで注目したいのは「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)

新規項目を追加するadd_todoitem関数

 基本構造はget_todoitems関数と同様だが、こちらは受け取ったデータ(request.jsonオブジェクト)に、"title"をキーとする要素が含まれているかをチェックして、それがある場合にだけ新規にデータを作成するようにしている。

 といったところが、app.pyファイルで重要な部分だ。最後にこのWeb APIの動作を確認しておこう。

Flask-Marshmallowを利用した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


「play game」というToDo項目を追加

 また、全項目の取得なら次のようになる。

> curl -H "Content-type: application/json" -X GET http://localhost:5000/api/todoitems


全項目を取得

 以下はPOSTリクエストで幾つかの項目を作成して、最後にGETリクエストで全項目を取得しているところだ。

Web APIの動作を確認しているところ Web APIの動作を確認しているところ

 ここでは、VS Codeのコマンドパレットから[Python: Create Terminal]コマンドを実行して、仮想環境が有効化されたターミナルを開き、そこで「flask run」コマンドを実行しアプリを起動した後に、[ターミナルの分割]ボタンをクリックして、1つのターミナルウィンドウにアプリからのメッセージとコマンドプロンプトを表示するようにしている。これにより、右側のコマンドプロンプトでcurlコマンドを実行すると、Web APIがどう反応しているかを左側のペーンで確認できる。

 ここまで、Flask-Marshmallowを利用してWeb APIを作成する方法を見てきた。次ページでは、これをより簡単に行ってくれるFlask-Restlessを使ってWeb APIを作成してみよう。

       1|2 次のページへ

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。