ペイの技術MEMO

文系大学生の技術メモ

FlaskでAPIを作る

はじめに

Flaskで簡単にAPIを作成してみます。Flaskは超軽量なフレームワークなので、書き方は割と自由です。

PythonのORMである、SQLAlchemyを利用し、接続先をsqliteのインメモリにします。

今回はテストでインメモリデータベースを使いますが、実行するたびにテーブルのデータは消えてしまうので、本番環境では利用しないようにお願いします。

環境構築

peei.hatenablog.com

前回環境構築について説明したので、これを元に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)

FlaskでREST APIを作ってみる – Nyle Engineering Blog – Medium

API — Flask 1.0.2 documentation