FlaskでAPIを作る
はじめに
Flaskで簡単にAPIを作成してみます。Flaskは超軽量なフレームワークなので、書き方は割と自由です。
PythonのORMである、SQLAlchemyを利用し、接続先をsqliteのインメモリにします。
今回はテストでインメモリデータベースを使いますが、実行するたびにテーブルのデータは消えてしまうので、本番環境では利用しないようにお願いします。
環境構築
- Python 3.7.2
- Flask 1.0.2
前回環境構築について説明したので、これを元にAPIのファイルを作成していきます。
ファイルを変更するたびに、実行しないといけないのも大変なので、flask run
で実行し、保存したら自動的に変更内容が反映されるようにします。
export FLASK_ENV=development export FLASK_APP=app.py
app.pyは今回APIを作成するファイルです。これでflask run
したときにこのファイルが実行されます。
また、FlakとSQLAlchemyを使うので、pip等で使えるようにしておきます。
pip install flask pip install Flask-SQLAlchemy
Hello World
まずは簡単にusers
にリクエストしたら、Hello Worldと返すようなAPIを作成します。
app.pyを以下のように変更します。
from flask import Flask, jsonify app = Flask(__name__) @app.route('/users') def list_user(): return jsonify({ 'message': 'Hello World' })
flask run
を実行します。
そしたら別のシェルを開き、curl等を投げてみます。
ただ、curlはオプションをつけると、少々複雑化してしまうので、今回はHTTPieをつかいます。
brew install httpie
早速リクエストしてみます。
http http://localhost:5000/users
HTTPieを使うと以下のようにレスポンスされるかと思います。
HTTP/1.0 200 OK Content-Length: 31 Content-Type: application/json Date: Sun, 31 Mar 2019 09:12:30 GMT Server: Werkzeug/0.15.1 Python/3.7.2 { "message": "Hello World" }
usersにアクセスすると、Flaskのライブラリであるjsonifyを利用してjson形式でレスポンスしているのが分かります。
SQLAlchemyを使ってみる
PythonのORMであるSQLAlchemyを環境構築でinstallしていると思うので、app.pyに読み込んで、そこにデータを保存していけるようにします。
from flask import Flask, request, jsonify #元からある from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) #元からある app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' db = SQLAlchemy(app) class User(db.Model): __tablename__ = 'hoge' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text) with app.app_context(): db.create_all()
以下のconfigでデータベースの接続先を記述しています。
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
'sqlite:///:memory:'
とすることで、sqliteのIn-memory上に一旦保存することができます。
インメモリ上ではflask run
(今回の場合はファイルを変更)するたびにデータベースに保存した内容は消えます。
SQLAlchemyの詳細は以下のクイックスタートが参考になりました。
Quickstart — Flask-SQLAlchemy Documentation (2.3)
クイックスタートではPythonのインタプリタを起動して、db.create_all()
をしています。
ただテーブルとデータベースを作成するのに、インタプリタを開くのは結構面倒なので、app.pyが読み込まれた時点でデータベースも初期化できるようにフックします。
それが以下の書き方です。
with app.app_context(): db.create_all()
CRUD
上記で準備ができたので、CRUD処理を書いていきます。
仕様としては、idは一意にして、nameをそれぞれCRUDできるようにします。
@app.route('/users') def list_user(): users = User.query.all() data = [ { 'id': i.id, 'name': i.name, } for i in users ] return jsonify(data) @app.route('/users', methods=['POST']) def create_user(): user = User() user.name = request.json['name'] db.session.add(user) db.session.commit() return jsonify({ 'id': user.id, 'name': user.name }), 201 @app.route('/users/<id>', methods=['PUT']) def update_user(id): user = User.query.get(id) if not user: return jsonify({ 'message': 'userは存在しません。' }), 404 user.name = request.json['name'] db.session.add(user) db.session.commit() return jsonify({ 'id': user.id, 'name': user.name }) return "", 201 @app.route('/users/<id>', methods=['DELETE']) def delete_user(id): user = User.query.get(id) if not user: return jsonify({ 'message': 'idが一致していません。' }), 404 db.session.delete(user) db.session.commit() return "", 201
GET
http http://localhost:5000/users HTTP/1.0 200 OK Content-Length: 3 Content-Type: application/json Date: Sun, 31 Mar 2019 09:58:05 GMT Server: Werkzeug/0.15.1 Python/3.7.2 []
POST
http POST http://localhost:5000/users name=hoge HTTP/1.0 201 CREATED Content-Length: 33 Content-Type: application/json Date: Sun, 31 Mar 2019 09:58:52 GMT Server: Werkzeug/0.15.1 Python/3.7.2 { "id": 1, "name": "hoge" }
PUT
http PUT http://localhost:5000/users/1 name=hogehoge HTTP/1.0 200 OK Content-Length: 37 Content-Type: application/json Date: Sun, 31 Mar 2019 09:59:25 GMT Server: Werkzeug/0.15.1 Python/3.7.2 { "id": 1, "name": "hogehoge" }
DELETE
http DELETE http://localhost:5000/users/1 HTTP/1.0 201 CREATED Content-Length: 0 Content-Type: text/html; charset=utf-8 Date: Sun, 31 Mar 2019 09:59:43 GMT Server: Werkzeug/0.15.1 Python/3.7.2
やっていることは単純でPOST
しているときは、idは一意なので、被ることはありません。
なので、nameをPOSTするだけでデータベースに保存されるようになっています。
PUT
はidを引数にとり、データベースに存在しなかったら更新できないようにしています。
DELETE
もidを引数にとり、データベースに存在しなかったら削除できないようにしています。
また、APIを叩き、status codeがわからないと、削除した時などは特に分かりづらいのでreturnする際にstatus codeも返すようにします。
さいごに
本来データベースに登録する処理はバリデート処理などを事前に挟めないといけませんが、今回はAPIを叩き、レスポンスをもらうことを中心に書いたので、別の機会で詳しく書きたいと思います。
参考
Quickstart — Flask-SQLAlchemy Documentation (2.3)