Flask
api
python2.7
peewee
RHEL7.3

軽い(自己満足の)APIを実装してみる

どうやってAPIって提供されているのか?

いつも使う側なので、提供側を経験したく。
インフラエンジニアだと、ミドルウェア設計までで終わっていて、あとはアプリに"はい、どうぞ"なので。

前準備

例のごとく、ネット環境無いのでパッケージから導入。

python-babel-0.9.6-8.el7.noarch.rpm
python-flask-0.10.1-4.el7.noarch.rpm
python-itsdangerous-0.23-2.el7.noarch.rpm
python-jinja2-2.7.2-2.el7.noarch.rpm
python-markupsafe-0.11-10.el7.x86_64.rpm
python-werkzeug-0.9.1-2.el7.noarch.rpm
python2-peewee-2.10.2-1.fc27.x86_64.rpm

実装

■まずはデータを用意

今回はデータベースにpeewee,webフレームワークにflaskを使う。
peeweeで以下のデータとimport用のスクリプトを用意し、一気にデータを作成する。

userlist.tsv
#タブ区切り
#user   firm    isprivileged
Us01    firm1   True
Us02    firm2   True
Us03    firm3   False
Us04    firm4   False
Us05    firm5   True
Us06    firm6   False
Us07    firm7   True
Us08    firm8   False

項目はタブ区切り。

importdata.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from peewee import *

db = SqliteDatabase('base.db')

class BaseModel(Model):
    class Meta:
        database = db

class User(BaseModel):
    user = TextField()
    firm = TextField()
    isprivileged = BooleanField()

#モデルの定義に基いてテーブル作成
User.create_table()
#ファイルをreadモードで開き、1行ずつ読み込む
for line in open("userlist.tsv", "r"):
    (user, firm, isprivileged) = tuple(line[:-1].split("\t"))
    if user == '#user': #1行目が見出しなので、読み込まないように
       pass
    else:
       #レコードを作成
       User.create(user=user,
                   firm=firm,
                   isprivileged=isprivileged)
       #レコードから値を取得して格納
       a = User.get(User.user == user).user
       b = User.get(User.user == user).firm
       c = User.get(User.user == user).isprivileged

       print 'created record : user=' + str(a) + ' firm=' + str(b) + ' privilegelevel=' + str(c)

以下はpeeweeのチュートリアルに載ってた、お約束事的なもの。

db = SqliteDatabase('base.db')
→DBはsqliteを使うよ、DBファイルはbase.dbを使うよ。

class BaseModel(Model):
class Meta:
database = db
→モデル定義し、それが上で言ってたDBと紐付くよ。
※peeweeではモデルなるものを作成して、DBを紐付けて操作をするらしい。

class User(BaseModel):
user = TextField()
firm = TextField()
isprivileged = BooleanField()
→BaseModelをオーバーライドして、Userというモデルを定義して、フィールドを定義。
※これは省略できるか?ただ、何個もモデルを持たせるなら、BaseModel定義はあった方がいい気がする。

上記を実行すると、以下の結果。

sample
# ./import.py
created record : user=Us01 firm=firm1 privilegelevel=True
created record : user=Us02 firm=firm2 privilegelevel=True
created record : user=Us03 firm=firm3 privilegelevel=True
created record : user=Us04 firm=firm4 privilegelevel=True
created record : user=Us05 firm=firm5 privilegelevel=True
created record : user=Us06 firm=firm6 privilegelevel=True
created record : user=Us07 firm=firm7 privilegelevel=True
created record : user=Us08 firm=firm8 privilegelevel=True
#

DBから値を取得するには、pythonのインタプリタに入った後、以下のように実行する。

model
>>>from peewee import *
>>>db = SqliteDatabase('base.db')
>>>class BaseModel(Model):
...  class Meta:
...     database = db
...
>>>
>>>class <Model>(BaseModel):
...    <Field1> = TextField()
...    <Field2> = TextField()
...    <Field3> = BooleanField()
...
>>>
>>><Model>.get(<Model>.<検索するField> == "検索キー").<値を取得したいField>

"<>"内は適宜読み替えてください。

■FlaskでAPI

以下のスクリプト作成。

api.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, jsonify, abort, make_response, Response, request, url_for
from peewee import *

#利用するDB定義 ここから--------
db = SqliteDatabase("base.db")

class BaseModel(Model):
    class Meta:
        database = db

class User(BaseModel):
    user = TextField()
    firm = TextField()
    isprivileged = TextField()
#ここまで----------------------

#Flask使うぜ!
api = Flask(__name__)

@api.route('/getUser/<string:users>', methods=['GET'])
def get_user(users):
    try:
        user = User.get(User.user == users)
    except User.DoesNotExist:
        abort(404)

    result = {
        "result":0,
        "data":{
            "User":user.user,
            "firm":User.firm,
            "isprivileged":User.isprivileged
            }
        }

    return make_response(jsonify(result))

@api.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Not found'}), 404)

if __name__ == '__main__':
    api.run(host='0.0.0.0', port=80)

上記を部分ごとに説明。

@api.route('/getUser/string:users', methods=['GET'])

・"@"でデコレートする。
・Flaskでは、ユーザがアクセス先と許可するメソッドを@api.route()の中に書く。
 apiは上でインスタンス化した"api = Flask(name)"なので、個々人の設定に沿う。
string:usersはユーザが入力した値を変数usersに格納する、ということ。

def get_user(users):
try:
user = User.get(User.user == users)
except User.DoesNotExist:
abort(404)

・ユーザがアクセスしてきた際の処理内容を定義する。
・ユーザが入力した変数usersを使って、"User.get(User.user == users)"で
 レコードが存在するか確認、なければ"abort(404)"する。
・abortはFlaskの便利屋さんで、"@api.errorhandler(404)"まで飛ばしてくれる。

return make_response(jsonify(result))

・resultに格納した内容をjson化して返却。

@api.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)

・エラーハンドリング。ここでabortした際の処理が発生。

api.run(host='0.0.0.0', port=80)

・実際に動かします。
・"host='0.0.0.0'"は自身のIPアドレスになる。
・ポートを指定。デフォルトは確か80。
・debugしたいなら、api.run()の中に"debug=True"を加える(カンマ区切り)。

■利用してみる

利用できる状態にするには、api.pyを実行します。

session
# ./api.py &
[1] 1738
#  * Running on http://0.0.0.0:80/

Enter押せばプロンプト戻ります。

この状態で、別のブラウザなりlinuxなりからアクセスする。
以下はLinuxの例。

session
# curl -i -k http://XXX.XXX.XXX.XXX:80/getUser/Us01
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 100
Server: Werkzeug/0.9.1 Python/2.7.5
Date: Thu, 15 Mar 2018 03:32:40 GMT

{
  "data": {
    "User": "Us01",
    "firm": "firm1",
    "isprivileged": true
  },
  "result": 0
}
#