FlaskでRESTful Webサービスを作成する
はじめに
Webサービス(Feedback)を用いて各種手順をご紹介します。
Mac環境の記事ですが、Windows環境も同じ手順になります。環境依存の部分は読み替えてお試しください。
目的
この記事を最後まで読むと、次のことができるようになります。
No. | 概要 | キーワード |
---|---|---|
1 | 開発環境の設定 | 環境変数 |
2 | ローカル/本番環境の設定 | instance_relative_config |
3 | 静的コード解析ツールの設定 | pylint |
4 | パッケージの管理 | pip |
5 | MTV(MVC)モデルの開発 | Flask-SQLAlchemy, Blueprint |
6 | DBのマイグレーション | flask, Flask-Migrate |
7 | Flaskの起動 | flask |
8 | クラアント側の処理(サンプル) | requests |
実行環境
環境 | Ver. |
---|---|
macOS Catalina | 10.15.2 |
Python | 3.7.3 |
Flask-Migrate | 2.5.2 |
Flask-SQLAlchemy | 2.4.1 |
Flask | 1.1.1 |
requests | 2.22.0 |
ソースコード
実際に実装内容やソースコードを追いながら読むとより理解が深まるかと思います。是非ご活用ください。
関連する記事
Feedback REST API
Service | Method | Endpoint | Example |
---|---|---|---|
Healthcheck | GET | /feedback/healthcheck | /feedback/healthcheck |
Read All | GET | /feedback/records | /feedback/records |
Read One | GET | /feedback/records/<> | /feedback/records/123 |
Create | POST | /feedback/records | /feedback/records |
Update | PUT | /feedback/records/<> | /feedback/records/123 |
Delete | DELETE | /feedback/records/<> | /feedback/records/123 |
0. 開発環境の構成
すべてのコマンドラインは、feedback-api
をカレントディレクトリとして実行します。
feedback-api
├── .env
├── .flaskenv
├── .pylintrc
├── .venv
├── .vscode
├── README.md
├── app
│ ├── __init__.py
│ ├── client
│ │ ├── __init__.py
│ │ ├── create.py
│ │ ├── delete.py
│ │ ├── read_all.py
│ │ ├── read_one.py
│ │ └── update.py
│ ├── config.py
│ ├── feedback
│ │ ├── __init__.py
│ │ ├── common
│ │ │ ├── __init__.py
│ │ │ └── utility.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ └── feedback.py
│ │ ├── static
│ │ │ └── __init__.py
│ │ ├── templates
│ │ │ └── __init__.py
│ │ └── views
│ │ ├── __init__.py
│ │ └── feedback.py
│ ├── requirements.txt
│ ├── run.py
│ └── tests
│ └── __init__.py
├── instance
│ ├── config.py
│ └── db.sqlite3
└── migrations
1. 開発環境の設定
feedback-api
├── .env
├── .flaskenv
├── .venv
├── .vscode
仮装環境を作成する
-
コマンドラインを実行する。
procedure.sh~$ python -m venv .venv
-
feedback-api配下に
.venv
フォルダが作成される。開発環境毎にパッケージ管理が可能となる。
仮装環境を設定する
-
コマンドラインを実行する。ターミナルに
(.venv)
と表示される。procedure.sh~$ source .venv/bin/activate (.venv) ~$
-
設定を解除する場合は
deactivate
を実行します。ターミナルから(.venv)
が消える。procedure.sh(.venv) ~$ deactivate ~$
環境変数を設定する
-
コマンドラインを実行する。
procedure.sh(.venv) ~$ export PYTHONPATH=app/ (.venv) ~$ export FLASK_APP=app/run.py (.venv) ~$ export FLASK_ENV=development
-
or 上記コマンドラインを
.flaskenv
ファイルに記述して実行する。procedure.sh(.venv) ~$ source .flaskenv
Visual Studio Code - 仮装環境を設定する
-
command + shift + p
を同時に押下してコマンドパレットを表示する。 -
Python: Select Interpreter
を選択する。 -
.venv
のパスを選択する。feedback-api配下に.vscode
フォルダが作成される。
Visual Studio Code - PYTHONPATHを設定する
本設定がない場合は、ディレクトリパスの異なるimportが認識されない。
-
feedback-api配下に
.env
ファイルを作成して以下を記述する。procedure.shPYTHONPATH=app/
-
Visual Studio Codeのウィンドウを再読み込みする。
2. ローカル/本番環境の設定
feedback-api
├── app
│ ├── config.py
│ ├── run.py
├── instance
│ ├── config.py
ローカル環境の設定値を定義する
-
feedback-api配下に
instance
フォルダを作成して、その配下にconfig.py
を作成する。config.py"""instance/config.py """ import os DEBUG = True # SECRET_KEY is generated by os.urandom(24). SECRET_KEY = '\xf7\xf4\x9bb\xd7\xa8\xdb\xee\x9f\xe3\x98SR\xda\xb0@\xb7\x12\xa4uB\xda\xa3\x1b' STRIPE_API_KEY = '' SQLALCHEMY_DATABASE_URI = 'sqlite:///{db_path}/{db_name}'.format(**{ 'db_path': os.path.dirname(os.path.abspath(__file__)), 'db_name': 'db.sqlite3' }) SQLALCHEMY_TRACK_MODIFICATIONS = True SQLALCHEMY_ECHO = True
本番環境の設定値を定義する
-
feedback-apiのapp配下に
config.py
を作成する。config.py"""app/config.py """ import os DEBUG = False SECRET_KEY = os.getenv('SECRET_KEY', '') STRIPE_API_KEY = os.getenv('STRIPE_API_KEY', '') SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI', '') SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_ECHO = False
-
環境変数を設定する。
procedure.sh(.venv) ~$ export SECRET_KEY=<本番設定値> (.venv) ~$ export STRIPE_API_KEY=<本番設定値> (.venv) ~$ export SQLALCHEMY_DATABASE_URI=<本番設定値> (.venv) ~$ export SQLALCHEMY_TRACK_MODIFICATIONS=<本番設定値> (.venv) ~$ export SQLALCHEMY_ECHO=<本番設定値>
Flaskから環境設定を読み込む
-
Flask()の引数に
instance_relative_config
を設定する。 -
True
を設定した場合はinstance/config.py
から設定値を読み込む。 -
False
を設定した場合はapp/config.py
から設定値を読み込む。run.py"""app/run.py """ # ~省略~ app = Flask(__name__, instance_relative_config=True) app.config.from_pyfile('config.py') # ~省略~
3. 静的コード解析ツールの設定
feedback-api
├── .pylintrc
├── .venv
├── app
ツールをインストールする
-
pylint
をインストールする。.venv
にインストールされる。procedure.sh(.venv) ~$ pip install pylint
ツールを設定する
-
設定ファイルを作成する。
procedure.sh(.venv) ~$ pylint --generate-rcfile > .pylintrc
-
不要なコードチェックを無効化する(サンプル)。
- 変数名の大文字チェックを無効化(invalid-name)
- ソースコードの類似チェックを無効化(too-few-public-methods)
- SQLAlchemyクラスの定義チェックを無効化(SQLAlchemy定義の参照で警告となるため)(SQLAlchemy)
- scoped_session関数の定義チェックを無効化(db.sessionの呼び出しで警告となるため)(scoped_session)
.pylintrc# ~省略~ disable=print-statement, # ~省略~ invalid-name, # add too-few-public-methods # add # ~省略~ ignored-classes=optparse.Values,thread._local,_thread._local, SQLAlchemy, # add scoped_session # add # ~省略~
ツールを実行する
-
pylint
を実行する。解析結果をみてソースコードを修正する。procedure.sh(.venv) ~$ pylint app/
4. パッケージの管理
feedback-api
├── .venv
├── app
│ ├── requirements.txt
個別でインストールする
-
コマンドラインを実行する。
procedure.sh(.venv) ~$ pip install Flask-Migrate (.venv) ~$ pip install Flask-SQLAlchemy (.venv) ~$ pip install Flask (.venv) ~$ pip install requests
パッケージリスト(requirements)を作成する
-
コマンドラインを実行する。
procedure.sh(.venv) ~$ pip freeze > app/requirements.txt
一括でインストールする
-
コマンドラインを実行する。
procedure.sh(.venv) ~$ pip install -r app/requirements.txt
個別でアンインストールする
-
コマンドラインを実行する。
procedure.sh(.venv) ~$ pip uninstall Flask-Migrate (.venv) ~$ pip uninstall Flask-SQLAlchemy (.venv) ~$ pip uninstall Flask (.venv) ~$ pip uninstall requests
一括でアンインストールする
-
コマンドラインを実行する。
procedure.sh(.venv) ~$ pip uninstall -r app/requirements.txt
5. MTV(MVC)モデルの開発
feedback-api
├── app
├── __init__.py
├── feedback
│ ├── __init__.py
│ ├── common
│ │ ├── __init__.py
│ │ └── utility.py
│ ├── models
│ │ ├── __init__.py
│ │ └── feedback.py
│ ├── static
│ │ └── __init__.py
│ ├── templates
│ │ └── __init__.py
│ └── views
│ ├── __init__.py
│ └── feedback.py
├── run.py
モデルの概要
機能 | 意味 | 概要 |
---|---|---|
M | Model | アプリケーションデータ、ビジネスルール、ロジック、関数。 |
V | View | グラフや図などの任意の情報表現。 |
C | Controller | 入力を受け取りmodelとviewへの命令に変換する。 |
T | Template | MVCモデルのV(View)相当。画面表示を担う。 |
一般モデルとFlask/Djangoモデルの対比と特徴
- Flask/Djangoモデルの
T:Template
は、一般モデルのV:View
に相当する。 - Flask/Djangoモデルの
V:View
は、一般モデルのC:Controller
に相当する。
Flask/Djangoモデル | 一般モデル | 参考 |
---|---|---|
M:Model | M:Model | app/feedback/models |
T:Template | V:View | app/feedback/templates |
V:View | C:Controller | app/feedback/views |
Resource | Resource | app/feedback/static |
M:Modelを作成する
-
SQLAlchemyのインスタンスを生成する。
__init__.py"""app/feedback/models/__init__.py """ from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() def init(): """init """ db.create_all()
-
SQLAlchemyのクラス(
db.Model
)を継承してModelを作成する。feedback.py"""app/feedback/models/feedback.py """ from datetime import datetime from feedback.models import db class Feedback(db.Model): """Feedback """ __tablename__ = 'feedback' id = db.Column(db.Integer, primary_key=True, autoincrement=True) service = db.Column(db.String(255), nullable=False) title = db.Column(db.String(255), nullable=False) detail = db.Column(db.String(255), nullable=False) created_date = db.Column( db.DateTime, nullable=False, default=datetime.utcnow) def __init__(self, service, title, detail): self.service = service self.title = title self.detail = detail def to_dict(self): """to_dict """ return { 'id': self.id, 'service': self.service, 'title': self.title, 'detail': self.detail, 'created_date': self.created_date }
T:Templateを作成する
- Flask/Djangoモデルの
T:Template
は、一般モデルのV:View
に相当する。HTMLを用いてTemplateを作成する。
本Webサービスは、View機能を持たないため作成しない。
V:Viewを作成する
-
Flask/Djangoモデルの
V:View
は、一般モデルのC:Controller
に相当する。Blueprint
でルートを定義してViewを作成する。feedback.py"""app/feedback/views/feedback.py """ from flask import Blueprint, jsonify, redirect, request, url_for from feedback.common.utility import err_response from feedback.models import db from feedback.models.feedback import Feedback feedback = Blueprint('feedback', __name__, url_prefix='/feedback') @feedback.route('/healthcheck', methods=['GET']) def healthcheck(): """healthcheck """ return jsonify({ 'status': 'healthy' }), 200 @feedback.route('/records', methods=['GET']) def read_all(): """read_all """ records = Feedback.query.all() return jsonify([record.to_dict() for record in records]), 200 @feedback.route('/records/', methods=['GET']) def redirect_read_all(): """redirect_read_all """ return redirect(url_for('feedback.read_all')) @feedback.route('/records/<int:record_id>', methods=['GET']) def read_one(record_id=None): """read_one """ record = Feedback.query.filter_by(id=record_id).first() return jsonify(record.to_dict()), 200 @feedback.route('/records', methods=['POST']) def create(): """create """ payload = request.json service = payload.get('service') title = payload.get('title') detail = payload.get('detail') record = Feedback(service=service, title=title, detail=detail) db.session.add(record) db.session.commit() response = jsonify(record.to_dict()) response.headers['Location'] = '/feedback/records/%d' % record.id return response, 201 @feedback.route('/records/<int:record_id>', methods=['PUT']) def update(record_id=None): """update """ record = Feedback.query.filter_by(id=record_id).first() payload = request.json record.service = payload.get('service') record.title = payload.get('title') record.detail = payload.get('detail') db.session.commit() return jsonify(record.to_dict()), 204 @feedback.route('/records/<int:record_id>', methods=['DELETE']) def delete(record_id=None): """delete """ record = Feedback.query.filter_by(id=record_id).first() db.session.delete(record) db.session.commit() return jsonify(None), 204 @feedback.errorhandler(404) @feedback.errorhandler(500) def errorhandler(error): """errorhandler """ return err_response(error=error), error.code
共通処理を作成する
-
Utilityを作成する。
utility.py"""app/feedback/common/utility.py """ from flask import jsonify def err_response(error): """err_response """ return jsonify({ "code": error.code, "name": error.name, "description": error.description, })
Flaskの起動処理を作成する
-
Flaskのインスタンスを生成する。
run.py"""app/run.py """ import os from flask import Flask from flask_migrate import Migrate from feedback.common.utility import err_response from feedback.models import db from feedback.views.feedback import feedback app = Flask(__name__, instance_relative_config=True) app.config.from_pyfile('config.py') app.register_blueprint(feedback) db.init_app(app) Migrate(app, db) @app.errorhandler(404) @app.errorhandler(500) def errorhandler(error): """errorhandler """ return err_response(error=error), error.code def main(): """main """ host = os.getenv('HOST', '0.0.0.0') port = int(os.getenv('PORT', '5000')) app.run(host=host, port=port) if __name__ == '__main__': main()
6. DBのマイグレーション
Flask-Migrateを使ってマイグレーションする
-
コマンドラインを実行する。
procedure.sh(.venv) ~$ flask db init (.venv) ~$ flask db migrate (.venv) ~$ flask db upgrade
Flask-Migrateを使わずにマイグレーションする
-
コマンドラインを実行する。
flask shell
の実行に環境変数の設定が必要となる。procedure.sh(.venv) ~$ flask shell >>> from feedback.models import init >>> init() >>> exit()
-
or 直接
create_all()
を実行する。procedure.sh(.venv) ~$ flask shell >>> from feedback.models import db >>> db.create_all() >>> exit()
7. Flaskの起動
-
コマンドラインを実行する。
flask run
の実行に環境変数の設定が必要となる。procedure.sh(.venv) ~$ flask run
8. クラアント側の処理(サンプル)
feedback-api
├── app
├── __init__.py
├── client
├── __init__.py
├── create.py
├── delete.py
├── read_all.py
├── read_one.py
└── update.py
レコードを全件取得する
"""app/client/read_all.py
"""
import requests
get_url = '{host}:{port}/feedback/records'.format(**{
'host': 'http://127.0.0.1',
'port': '5000'
})
res = requests.get(url=get_url)
print(res.status_code)
print(res.text)
レコードを1件取得する
"""app/client/read_one.py
"""
import requests
get_url = '{host}:{port}/feedback/records/{record_id}'.format(**{
'host': 'http://127.0.0.1',
'port': '5000',
'record_id': '1'
})
res = requests.get(url=get_url)
print(res.status_code)
print(res.text)
レコードを作成する
"""app/client/create.py
"""
import json
import requests
post_url = '{host}:{port}/feedback/records'.format(**{
'host': 'http://127.0.0.1',
'port': '5000'
})
post_data = json.dumps({
'service': 'create_service',
'title': 'create_title',
'detail': 'create_detail'
})
post_headers = {
'Content-Type': 'application/json'
}
res = requests.post(url=post_url, data=post_data, headers=post_headers)
print(res.status_code)
print(res.text)
レコードを更新する
"""app/client/update.py
"""
import json
import requests
update_url = '{host}:{port}/feedback/records/{record_id}'.format(**{
'host': 'http://127.0.0.1',
'port': '5000',
'record_id': '1'
})
update_data = json.dumps({
'service': 'update_service',
'title': 'update_title',
'detail': 'update_detail'
})
update_headers = {
'Content-Type': 'application/json'
}
res = requests.put(url=update_url, data=update_data, headers=update_headers)
print(res.status_code)
print(res.text)
レコードを削除する
"""app/client/delete.py
"""
import requests
delete_url = '{host}:{port}/feedback/records/{record_id}'.format(**{
'host': 'http://127.0.0.1',
'port': '5000',
'record_id': '1'
})
res = requests.delete(delete_url)
print(res.status_code)
print(res.text)