#はじめに
社内のオンライン開発合宿というイベント向けに、アプリケーションを作る一環として、
(コロナ禍もあってオンライン...)
-
Python
を開発で使ってみたい -
Flask
が軽量フレームワークでDjango
よりも学習コストが低くく、とっかかりやすそう
という理由で、バックエンドのアーキテクチャに組み込んで、試してみました◎
▼DBマイグレーションとORMの話はこちら
Flask(フラスク)は、プログラミング言語Python用の、軽量なウェブアプリケーションフレームワークである。標準で提供する機能を最小限に保っているため、自身を「マイクロフレームワーク」と呼んでいる。Werkzeug WSGIツールキットとJinja2テンプレートエンジンを基に作られている。BSDライセンスで公開されている。
参照元:wikipedia
ふむふむ。
つまり、Flask
自体の機能は最小限だから、自分の好きなように後でカスタマイズしてね
ってことか。
###Flaskの特徴
- 標準で提供する機能は最小限
- 拡張ライブラリは第三者によって提供
- オブジェクトリレーショナルマッパ
- フォーム値の検証
- ファイルのアップロード
- ユーザログイン
- 種々のオープンな認証技術 等々
#自分のFlaskアプリで使うライブラリの準備
Flask
自体と、それにまつわる拡張ライブラリをインストールしていきます。
※[#] 以降に2021/05/14時点のversionを記載
※Flask-Cors
がpython3.7までのサポートとなっていたので、Python3.7で開発してます。
$ pip install Flask # 1.1.2
$ pip install Flask-Cors # 3.0.10
$ pip install python-dotenv # 0.17.1
$ pip install PyMySql # 1.0.2
$ pip install Flask-SQLAlchemy # 2.5.1
$ pip install flask-marshmallow # 0.14.0
$ pip install marshmallow-sqlalchemy # 0.25.0
#自分のFlaskアプリのファイル構成
src
├── .env #環境変数の登録
├── main.py #アプリ起動
├── router.py #APIエンドポイントの集約。ルーター目的
├── db.py #dbインスタンスの初期化
├── settings.py #[.env]から環境変数の読込と設定
├── logger.py #ロギング用のデコレータ
├── auth.py #APIの実行認可、認証目的
├── controller
│ └── user_controller.py #コントローラー
├── service
│ └── user_service.py #ビジネスロジック
├── model
│ └── users.py #モデル
├── config
│ ├── logging.json #ロギングの設定ファイル
│ └── config.py #DBの各種設定
└── logs
└── application.log #ログが出力されるファイル
#Flask(Python)で書いたコード
各ファイル自体の目的を明確にして、その用途にあった必要なコードのみの記述を意識しました。
##今回の目的:FlaskのRest APIで主にやること
DBに保存したユーザー情報を、APIを叩いて取得するまでの実践。
##main.py
でアプリ起動
- DB関連の初期化
- アプリ起動
- アプリに必要なインスタンスを
Blueprint
機能を使って登録(router)
(__init__.py
とか作ってファイルを分けた方がいいかも?)
#!/usr/bin/python3
from flask import Flask
from flask_cors import CORS
import router
from config import config
import db
def create_app():
# Generate Flask App Instance
app = Flask(__name__)
# Read DB setting & Initialize
app.config.from_object(config.Config)
db.init_db(app)
db.init_ma(app)
# Register Router Instance
app.register_blueprint(router.router)
# Additional Configuration
app.config['JSON_AS_ASCII'] = False #日本語文字化け対策
app.config["JSON_SORT_KEYS"] = False #ソートをそのまま
CORS(
app,
resources = {
r"/api/*": {"origins": ["http://localhost", "http://localhost:4200"]}
}
)
return app
app = create_app()
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True, port=8080, threaded=True, use_reloader=False)
##router.py
でAPIエンドポイントの管理
コントローラーへのルーティング設定を管理しています。
from flask import Blueprint
from controller import user_controller
from logging import config
from json import load
import auth
import logger
# Generate Router Instance
router = Blueprint('router', __name__)
# Read Logging Configuration
with open("./config/logging.json", "r", encoding="utf-8") as f:
config.dictConfig(load(f))
@router.route("/", methods=['GET'])
@logger.http_request_logging
@auth.requires_auth
def hello_world():
return "Hello World!!"
@router.route("/api/v1/users/getUserList", methods=['GET'])
@logger.http_request_logging
@auth.requires_auth
def api_v1_users_get_user_list():
return user_controller.get_user()
@router.after_request
def after_request(response):
# response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return response
##controller
でシンプルに結果を返す
クライアント側にビジネスロジックで生成物をレスポンスする。
from service.user_service import get_user_logic
def get_user():
return get_user_logic()
##service
でビジネスロジックを管理
DBから抽出したRawデータを加工する目的。
from flask import make_response, jsonify
from model.users import User, UserSchema
def get_user_logic():
users = User.get_user_list()
user_schema = UserSchema(many=True)
return make_response(jsonify({
'code': 200,
'users': user_schema.dump(users)
}))
##model
でDBに問い合わせ
Userモデルの定義と、CRUD系のメソッドを用意します。
from db import db, ma
from sqlalchemy.dialects.mysql import TIMESTAMP as Timestamp
from sqlalchemy.sql.functions import current_timestamp
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(225), nullable=False)
created_at = db.Column(Timestamp, server_default=current_timestamp(), nullable=False)
updated_at = db.Column(Timestamp, server_default=current_timestamp(), nullable=False)
# Contructor
def __init__(self, id, name, created_at, updated_at):
self.id = id
self.name = name
self.created_at = created_at
self.updated_at = updated_at
def __repr__(self):
return '<User %r>' % self.name
def get_user_list():
# SELECT * FROM users
user_list = db.session.query(User).all()
if user_list == None:
return []
else:
return user_list
def get_user_by_id(id):
return db.session.query(User)\
.filter(User.id == id)\
.one()
def create_user(user):
record = User(
name = user['name'],
)
# INSERT INTO users(name) VALUES(...)
db.session.add(record)
db.session.commit()
return user
# Difinition of User Schema with Marshmallow
# refer: https://flask-marshmallow.readthedocs.io/en/latest/
class UserSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = User
##db.py
でdbインスタンスの初期化
Marshmallow
というSQLAlchemy
で受け取ったデータをJSONに変換してくれるライブラリも起動して、Flask
アプリのapp
に関連づけます。
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
db = SQLAlchemy()
ma = Marshmallow()
def init_db(app):
db.init_app(app)
def init_ma(app):
ma.init_app(app)
##config.py
でdb情報の設定を管理
settings.py
で設定したコンスタントを使って各種プロパティを指定。
import settings
class SystemConfig:
# Flask
DEBUG = True
# SQLAlchemy
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@{host}/{db}?charset=utf8mb4'.format(**{
'user': settings.MYSQL_USER,
'password': settings.MYSQL_PASSWORD,
'host': settings.MYSQL_HOST,
'db': settings.MYSQL_DATABASE
})
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
Config = SystemConfig
##settings.py
で環境変数を定数にセット
# coding: UTF-8
import os
from os.path import join, dirname
from dotenv import load_dotenv
dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)
# Set Environment Variables to Constant
AP = os.environ.get("API_KEY")
MYSQL_ROOT_PASSWORD = os.environ.get("MYSQL_ROOT_PASSWORD")
MYSQL_HOST = os.environ.get("MYSQL_HOST")
MYSQL_DATABASE = os.environ.get("MYSQL_DATABASE")
MYSQL_USER = os.environ.get("MYSQL_USER")
MYSQL_PASSWORD = os.environ.get("MYSQL_PASSWORD")
##auth.py
でAPIに対する認証・認可周りを管理
ここで、APIの共通の認証処理ができるようになるデコレータ用の関数です。
HttpヘッダーのAuthorization
を使う場合に、何かしらできるみたい。
from flask import request
from functools import wraps
def requires_auth(func):
@wraps(func)
def wrapper(*args, **kwargs):
auth = request.headers.get("Authorization", None)
# ここで 認証処理 (...まだ書いていない)
return func(*args, **kwargs)
return wrapper
##logger.py
で共通のロギング関数デコレータを用意
APIを叩いた時に、ログを残せるようにしてます。
from flask import request, current_app
from functools import wraps
def http_request_logging(f):
@wraps(f)
def decorated_function(*args, **kwargs):
logger = current_app.logger
try:
logger.info('%s - %s - %s - %s', request.remote_addr, request.method, request.url, request.query_string)
except Exception as e:
logger.exception(e)
pass
return f(*args, **kwargs)
return decorated_function
##logging.json
の設定内容
一例です。この設定を元に、loggerが頑張ってログ出力してくれます。
console
とファイル出力ができるようにしてます。
{
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] [%(levelname)s] : %(message)s"
}
},
"loggers": {
"file": {
"handlers": ["file"],
"level": "WARN",
"qualname": "file",
"propagate": "no"
},
"wsgi": {
"handlers": ["wsgi"],
"level": "WARN",
"qualname": "wsgi",
"propagate": "no"
}
},
"handlers": {
"file": {
"class": "logging.handlers.TimedRotatingFileHandler",
"formatter": "default",
"filename": "./logs/application.log"
},
"wsgi": {
"class": "logging.StreamHandler",
"stream": "ext://flask.logging.wsgi_errors_stream",
"formatter": "default"
}
},
"root": {
"level": "WARN",
"handlers": ["file", "wsgi"]
}
}
##application.log
に出力される内容
[2021-05-14 11:06:28,844] [INFO] : * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
[2021-05-14 11:06:28,845] [INFO] : * Restarting with stat
[2021-05-14 11:06:29,080] [WARNING] : * Debugger is active!
[2021-05-14 11:06:29,090] [INFO] : * Debugger PIN: 143-258-047
#Flask REST API の実践と動作検証!!
main.py
がある階層に移動して、アプリ起動!!
$ cd /Users/username/src/github.com/flask-challenge/api/src
$ python main.py
* Serving Flask app "main" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
[2021-05-14 14:37:17,400] [INFO] : * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
[2021-05-14 14:37:17,402] [INFO] : * Restarting with stat
[2021-05-14 14:37:17,694] [WARNING] : * Debugger is active!
[2021-05-14 14:37:17,704] [INFO] : * Debugger PIN: 143-258-047
アプリ起動できましたね〜
###レスポンスが返ってきた!!!!
嬉しす✨
#Flaskでハマったポイント
-
router
を独立したファイルにさせたいと思い、Blueprint
の機能にたどり着いた。- ただ、
Blueprint
の機能が最初わからず、調べてもFlaskのtemplate(html)
と関連した記事が結構出て戸惑った - =>なんやかんやで、
Flask
アプリ(appのこと)に、他でBlueprintとして起動したインスタンスを登録できるシンプルな機能だった。(便利)
- ただ、
-
flask_cors
の互換性が盲点だった。エラると嫌だからバージョンをPython3.7
に下げた -
logger
やauth
の実装をする時に、デコレータというPythonの概念でつまずいた。- 2時間くらいデコレータのソースを見つめて、動画とか見て感覚を養ったら想像できるようになった。
- 結局は、元の関数の処理に、前処理として何か追加できるようになるよってこと。関数の装備品。
-
Marshmallow
の英語の発音が難しすぎた-
SQLAlchemy
と合わせて使うようで、結構調べる時間が必要だった
-
#FlaskとPython触った感想とまとめ
軽量フレームワークというだけあって、最初の導入はめちゃくちゃ楽だった。
import Flask
でアプリ起動すれば、すぐ使えるこの手軽さはやばい。
API
とかすぐ作れちゃうなこれ。
色々ライブラリも揃ってて組み合わせするともっと楽しそうだこれ。
しかもPython自体もすげえ感覚的にかける言語で、Javascript
にも似てるし、かなりストレスフレー!!
勉強にもなったし、最初のとっかかりにはとてもよかった。
業界で屈指のPythonフレームワークであるDjangoも、これから経験積んで色んな開発に携われるようになりたいでごわす。
こちらも参考になれば嬉しいです✨
以上、ありがとうございました!