とほほのFlask入門
Flaskとは
- 「フラスク」と呼ばれます。理科実験で使用する「フラスコ」と同じ単語です。
- Pythonベースの軽量なWebフレームワークです。マイクロフレームワークとも呼ばれます。
- 高機能・重厚な
Djangoと、シンプル・軽量なFlaskとして使い分けられています。 - オーストリアの Armin Ronacher がエイプリールフールのジョークとして開発したのがきっかけだそうです。
Flaskに関連してWerkzeug(WSGIライブラリ)、Jinja2(テンプレートエンジン)なども開発しています。- 彼のハンドルは mitsuhiko で、
Jinja2も、テンプレート → テンプル → 神社 から命名されています。日本好きっぽいですね。 - 2010年4月に初版の0.1、2018年4月に1.0、2021年5月に2.0がリリースされました。
- Python 3.6 以上(非同期機能を利用する場合は 3.7以上)を必要とします。
インストール
下記の環境を想定しています。
OS: Ubuntu 20.04 LTS Python: 3.8.10 Flask: 2.0.2
下記でインストールします。
$ sudo apt update # 必要に応じてアップデートする $ sudo apt -y upgrade # 必要に応じてアップグレードする $ sudo apt -y install python3 python3-pip $ sudo pip install Flask
チュートリアル
Hello world!
http://~/ にアクセスすると Hello world! を返却するサンプルです。hello.py として作成してください。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello world!</p>"
プログラム名を環境変数 FLASK_APP に設定し、flask run を実行します。対象ファイルが wsgi.py または app.py で PYTHONPATH で参照可能であれば、FLASK_APP の設定は省略できます。
$ export FLASK_APP=hello $ flask run
他ホストからの接続を受け付ける場合は -h 0.0.0.0 を、ポートを指定するには -p port を指定します。--reload をつけるとプログラム修正時に自動的にリロードします。
$ flask run -h 0.0.0.0 -p 5000
http://~/ にアクセスして Hello world! が表示されれば成功です。
プログラムに下記を追加して
from flask import Flask
app = Flask(__name__)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
下記の様に実行することもできます。
$ python3 hello.py
デバッグモード
FLASK_ENV に development を指定するとデバッグモードで動きます。デバッグモードでは、プログラムを修正すると自動的にリロードし、エラーが発生した際に、ブラウザにエラーの詳細情報が表示されます。
$ export FLASK_ENV=development $ flask run
ルーティング指定
@app.route()
@app.route() で、どのURLにどのメソッドでアクセスされると、どのメソッドを呼び出すかを指定します。下記は /users に対して GET メソッドでアクセスされたら users メソッドを呼び出します。
@app.route("/users")
def users():
...
下記の様に引数を受け取ることもできます。
@app.route("/users/<user_name>")
def users(user_name):
...
引数には型を指定することもできます。型には string(デフォルト), int, float, path, uuid が指定できます。
@app.route("/users/<int:user_id>")
def users(user_id):
...
GET 以外のメソッドに対応するには methods を指定します。
@app.route("/users", methods=["GET", "POST"])
def users():
...
@app.route() の代わりに app.add_url_rule() を使用することもできます。
app = Flask(__name__)
app.add_url_rule("/users", view_func=users)
URL末尾のスラッシュ
@app.route() で指定するURL末尾にスラッシュをつけると /foo でアクセスしても /foo/ にリダイレクトされて、スラッシュ有りでも無しでもアクセスできますが、つけない場合はスラッシュ無しでしかアクセスできません。
@app.route("/foo/") # /foo でも /foo/ でもアクセスできる
@app.route("/baa") # /baa はアクセス可。/baa/ はアクセス不可
データを受け取る
requestオブジェクト
メソッドの中では request オブジェクトを参照できます。リクエストに関する情報が含まれています。
from flask import request
@app.route("/users")
def users():
print(request.method)
メソッド・パス情報
メソッドやパス情報として下記などを参照できます。
request.method メソッド(GET) request.url URL(http://127.0.0.1:5000/users?uid=U12345) request.host_url ホストURL(http://127.0.0.1:5000/) request.scheme スキーマ(http) request.host ホスト(127.0.0.1:5000") request.path パス名(/users) request.query_string クエリ文字列(uid=U12345)
リクエストパラメータ
GETリクエストのパラメータは下記で参照できます。
request.args[key] GETパラメータ(Keyがなければエラー) request.args.get(key) GETパラメータ(KeyがなければNone) request.args.get(key, "...") GETパラメータがない場合のデフォルト値を指定 request.args.get(key, type=int) GETメータをint型で受け取る
POSTリクエストのパラメータは下記で参照できます。
request.form[key] POSTパラメータ(Keyがなければエラー) request.form.get(key) POSTパラメータ(KeyがなければNone) request.form.get(key, "...") POSTパラメータがない場合のデフォルト値を指定 request.form.get(key, type=int) POSTメータをint型で受け取る
JSONデータは下記で参照できます。
request.get_json() ボディのJSON。送信側が Content-Type: application/json で送信する必要あり request.json get_json()と同じ
その他のリクエスト情報
クライアント情報として下記などを取得できます。
request.accept_charsets 受付可能なキャラクタセット request.accept_encodings 受付可能なエンコーディング request.accept_languages 受付可能な言語 request.accept_mimetypes 受付可能なMIMEタイプ request.remote_addr リモートアドレス request.remote_user リモートユーザ
リクエスト情報として下記などを取得できます。
request.date 日時 request.content_type コンテントタイプ request.referrer 遷移元URL request.get_data() ボディ(バイナリ)
下記でヘッダ情報を参照できます。
request.headers.get("Host") 特定ヘッダ(KeyがなければNone)
request.headers["Host"] 特定ヘッダ(Keyがなければエラー)
下記で環境変数を参照できます。
request.environ.get("PATH") 特定の環境変数(Keyが無ければNone)
request.environ["PATH"] 特定の環境変数(Keyが無ければエラー)
その他の情報については下記を参照してください。
参考:https://flask.palletsprojects.com/en/2.0.x/api/#flask.Request
ファイルアップロード
ファイルのアップロードを受け付けてサーバ側に保存するサンプルは下記の様になります。
<form method="POST" action="/upload" enctype="multipart/form-data"> <input type="file" name="datafile"> <button>OK</button> </form>
@app.route("/upload", methods=["POST"])
def upload():
f = request.files["datafile"]
f.save("/tmp/datafile")
return "Received: " + f.filename
参考:https://flask.palletsprojects.com/en/2.0.x/patterns/fileuploads/
データを返却する
レスポンスデータ
メソッドの戻り値は HTML などのボディデータを指定します。
@app.route("/")
def main():
return "<!DOCTYPE html><html><head>..."
JSONを返却する場合はオブジェクトをそのまま返却することもできます。
@app.route("/")
def main():
return {"name": "Yamada"}
make_response() を使用すると レスポンスヘッダ や Cookie を返却することが可能になります。
from flask import make_response
@app.route("/")
def main():
resp = make_response("Return data...")
return resp
HTTPステータス
2番目の戻り値にHTTPステータスを返却することができます。
return "...", 404
return {...}, 404
return resp, 404
テンプレートファイル
Flask には Jinja2 と呼ばれるテンプレートエンジンが含まれています。テンプレートは 下記の様に templates フォルダに設置する必要があります。
main.py
templates
main.html
下記の様にテンプレートファイルを名を指定してその内容を返却します。
from flask import render_template
@app.route("/")
def main():
return render_template("main.html")
テンプレートにパラメータを渡すこともできます。
render_template("main.html", name="Yamada", age=26)
<div>Name: {{ name }}</div>
<div>Age: {{ age }}</div>
オブジェクトやクラスインスタンスを渡すこともできます。オブジェクトで渡す場合、クラスインスタンスで渡す場合どちらでも、テンプレート側では p.name でも p["name"] でも受け取ることができます。
p = { "name": "Yamada", "age": 26 }
render_template("main.html", p=p)
<div>Name: {{ p.name }}</div>
<div>Age: {{ p.age }}</div>
リストを渡すこともできます。
users = [ "Yamada", "Tanaka", "Suzuki" ]
return render_template("main.html", users=users)
{% for user in users %}
<div>{{ user }}</div>
{% endfor %}
Jinja2 に関する詳細は下記を参照してください。
参考:https://jinja.palletsprojects.com/en/3.0.x/
スタティックファイル
スタティックファイルは static フォルダ配下に置きます。
main.py
static
css
common.css
img
sample.png
js
common.js
<!doctype html> <html lang="ja"> <head> <title>MAIN</title> <link rel="stylesheet" href="/static/css/common.css"> <script src="/static/js/common.js"></script> </head> <body> <h1>Main</h1> <img src="/static/img/sample.png"> </body> </html>
レスポンスヘッダ
レスポンスヘッダを指定するには下記の様にします。
@app.route("/")
def main():
resp = make_response("OK")
resp.headers["X-TEST-HEADER"] = "This is test header."
return resp
Cookie
Cookie を読み取るには request.cookies、設定するには set_cookie() を使用します。
@app.route("/")
def main():
user_name = request.cookies.get("user_name")
resp = make_response("...")
resp.set_cookie("user_name", user_name)
return resp
参考:https://flask.palletsprojects.com/en/2.0.x/api/#flask.Request.cookies
リダイレクト
他のページにリダイレクトするには、redirect() を使用します。
from flask import redirect
@app.route("/")
def main():
return redirect("/login")
参考:https://flask.palletsprojects.com/en/2.0.x/api/#flask.redirect
URLを関数名で指定する
環境によってエンドポイントのパス名も変わることがあるため、URLは絶対パスで指定しないほうがよいとされています。url_for() を用いることで "do_login" というメソッド名の文字列から、そのメソッドに対応するURLを得ることができます。
from flask import url_for
@app.route("/login")
def do_login():
...
@app.route("/")
def main():
return redirect(url_for("do_login"))
下記の様に引数付きのURLを得ることもできます。
@app.route("/users/<user_id>")
def get_user(user_id):
...
@app.route("/")
def main():
print(url_for("get_user", user_id="U12345")) # => "/users/U12345"
return "OK\n"
下記の様にテンプレートの中で使用することもできます。
<a href="{{ url_for("get_user", user_id="U12345") }}">...</a>
関数名 "static" はスタティックディレクトリを示します。
<link rel="stylesheet" href="{{ url_for("static", filename="css/common.css") }}">
<script src="{{ url_for("static", filename="js/common.js") }}"></script>
<img src="{{ url_for("static", filename="img/sample.png") }}">
その他ノウハウ
エラーページのカスタマイズ
なんらかのエラーが発生した場合に Flask が返却するエラーページをカスタマイズすることができます。@app.errorhandler() の代わりに app.register_error_handler(404, not_found) で登録することもできます。
app = Flask(__name__)
@app.errorhandler(404)
def not_found(error):
return render_template("error_404.html"), 404
JSONを扱う
JSONデータを受け取るには、クライアントから Content-Type: application/json ヘッダをつけて渡してやる必要があります。
$ curl -X POST -H "Content-Type: application/json" http://localhost:5000/ -d '{"name":"Tanaka"}'
データ返却時は、オブジェクトをそのまま返却します。JSONを文字列として返却する場合は レスポンスヘッダ に Content-Type: application/json を指定します。
@app.route("/")
def main():
print request.json
return { "name": "Yamada", "age": 26 }
セッション
セッションを用いたアクセス回数表示サンプルです。5以上でクリアしています。app.secret_key にシステム毎に異なるランダムデータを設定しておく必要があります。セッション情報は secret_key で暗号化され、ブラウザの Cookie に保存されますので、あまり大きなデータを格納しようとすると Cookie の上限によってうまく保存できなくなります。
app = Flask(__name__)
app.secret_key = b"efb94fcefa1ef7f281d69a979cdf251b2b9bdd8b770d7a0fbfb9427287fec9f6"
@app.route("/")
def main():
count = session.get("count", 0)
count = count + 1
session["count"] = count
if count >= 5:
session.clear()
return "count = %d" % count
ロギング
INFO 以上のログを標準エラー出力とログファイルに書き出すサンプルです。Python 標準の logging を使用しているので、詳細はそちらを参照してください。ログ設定の変更時はサーバを再起動する必要があります。デバッグモードの場合は常にすべてのログが出力されます。
from flask import Flask
from logging.config import dictConfig
dictConfig({
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
"formatter": "default"
},
"file": {
"class": "logging.FileHandler",
"filename": "/tmp/flask.log",
"formatter": "default"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "file"]
},
"disable_existing_loggers": False,
})
app = Flask(__name__)
@app.route("/")
def main():
app.logger.debug("This is debug message.")
app.logger.info("This is info message.")
app.logger.warning("This is warning message.")
app.logger.error("This is error message.")
app.logger.critical("This is critical message.")
return "OK"
前処理・後処理
すべてのリクエストの前処理・後処理ハンドラを登録することができます。スタティックファイル呼び出しの際にも呼ばれることに注意してください。
def before_handler():
print("=== Before URL handler")
def after_handler(response):
print("=== After URL handler")
return response
app.before_request(before_handler)
app.after_request(after_handler)
グローバルオブジェクト
g というグローバルオブジェクトに、1回のリクエストの間で有効なグローバル情報を保存することができます。
from flask import g
g.sample_data = "Sample"
if "sample_data" in g:
print(g.sample_data)
コンフィグ
app.config はコンフィグ情報を管理します。
app = Flask(__main__) app.config["DEBUG"] = True
from_pyfile(), from_envvar(), form_json(), from_object() などを使用して各種フォーマットのコンフィグファイルから一括して読み込むこともできます。
app.config.form_json("config.json")
コンフィグファイルは環境別にソースファイルとは別の場所に置くことが推奨されます。下記の様にしてコンフィグファイルを /etc/myapp 配下に置くことができます。
app = Flask(__name__, instance_path="/etc/myapp", instance_relative_config=True)
app.config.from_pyfile("config.py")
標準のコンフィグ情報には下記などがあります。アプリケーション専用のコンフィグ値を定義することもできます。
ENV production DEBUG False TESTING False PROPAGATE_EXCEPTIONS None PRESERVE_CONTEXT_ON_EXCEPTION None SECRET_KEY None PERMANENT_SESSION_LIFETIME datetime.timedelta(days=31) USE_X_SENDFILE False SERVER_NAME None APPLICATION_ROOT / SESSION_COOKIE_NAME session SESSION_COOKIE_DOMAIN None SESSION_COOKIE_PATH None SESSION_COOKIE_HTTPONLY True SESSION_COOKIE_SECURE False SESSION_COOKIE_SAMESITE None SESSION_REFRESH_EACH_REQUEST True MAX_CONTENT_LENGTH None SEND_FILE_MAX_AGE_DEFAULT None TRAP_BAD_REQUEST_ERRORS None TRAP_HTTP_EXCEPTIONS False EXPLAIN_TEMPLATE_LOADING False PREFERRED_URL_SCHEME http JSON_AS_ASCII True JSON_SORT_KEYS True JSONIFY_PRETTYPRINT_REGULAR False JSONIFY_MIMETYPE application/json TEMPLATES_AUTO_RELOAD None MAX_COOKIE_SIZE 4093
詳細:https://flask.palletsprojects.com/en/2.0.x/config/#builtin-configuration-values
クラスメソッドを呼び出す
as_view() を用いることで、ハンドラとしてクラスメソッドを呼び出すことができます。クラスは dispatch_request() メソッドを実装しておく必要があります。methods でHTTPメソッドを指定できます。
from flask import Flask
from flask.views import View
app = Flask(__name__)
class GetUser(View):
methods = ["GET", "POST"]
def dispatch_request(self, user_id):
return "GET_USER(%s)\n" % user_id
app.add_url_rule("/users/<user_id>", view_func=GetUser.<em>as_view("get_users")</em>)
実装サンプル
ログイン認証
セッションを用いてでログインを行うサンプルです。Flask-Login というライブラリもよく利用されます。
from flask import Flask, request, session, render_template, redirect, url_for app = Flask(__name__) app.secret_key = b"efb94fcefa1ef7f281d69a979cdf251b2b9bdd8b770d7a0fbfb9427287fec9f6" # @login_requiredデコレータの実装 # 関数呼び出し前にセッションを確認してNoneであればログイン画面にリダイレクトする def login_required(func): import functools @functools.wraps(func) def wrapper(*args, **kwargs): if session.get("username") is None: return redirect(url_for("login")) else: return func(*args, **kwargs) return wrapper # メイン画面(ログイン認証が必要) @app.route("/") @login_required def main(): return render_template("main.html") # メンバ画面(ログイン認証が必要) @app.route("/member") @login_required def member(): return render_template("member.html") # ログイン画面 # ログインが成功するとセッションにusernameを設定する @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": username = request.form.get("username") password = request.form.get("password") if check_password(username, password): session["username"] = username return redirect(url_for("main")) else: return render_template("login.html", error_msg="Login error.") else: return render_template("login.html") # ログアウト画面(セッションをクリアする) @app.route("/logout") def logout(): session.clear() return render_template("login.html") # パスワードチェック関数 # 簡易的にusernameとpasswordが合致すればOKとしている def check_password(username, password): return username == password
REST-APIサンプル
MariaDB に接続してユーザ情報の CRUD(Create, Read, Update, Delete) を行う簡単な REST-API のサンプルです。エラー処理は省略しています。
from flask import Flask, request
import mysql.connector
app = Flask(__name__)
db = mysql.connector.connect(host="...", user="...", password="...", database="...")
def dbExec(sql, params={}):
cursor = db.cursor()
cursor.execute(sql, params)
result = cursor.fetchall()
cursor.close()
return result
@app.route("/users")
def listUsers():
result = dbExec("SELECT user_id, email FROM users")
users = []
for (user_id, email) in result:
users.append({"user_id": user_id, "email": email})
return {"users": users}
@app.route("/users", methods=["POST"])
def addUser():
params = {"user_id": request.json["user_id"], "email": request.json["email"]}
dbExec("INSERT INTO users VALUES (%(user_id)s, %(email)s)", params)
return {"result": "OK"}
@app.route("/users/<user_id>")
def getUser(user_id):
params = {"user_id": user_id}
result = dbExec("SELECT user_id, email FROM users WHERE user_id = %(user_id)s", params)
return {"user_id": result[0][0], "email": result[0][1]}
@app.route("/users/<user_id>", methods=["PATCH"])
def updateUser(user_id):
params = {"user_id": user_id, "email": request.json["email"]}
dbExec("UPDATE users SET email = %(email)s WHERE user_id = %(user_id)s", params)
return {"result": "OK"}
@app.route("/users/<user_id>", methods=["DELETE"])
def deleteUser(user_id):
params = {"user_id": user_id}
dbExec("DELETE FROM users WHERE user_id = %(user_id)s", params)
return {"result": "OK"}
下記の様に呼び出します。
$ curl http://localhost:5000/users
$ curl -X POST -H "Content-Type: application/json" http://localhost:5000/users \
-d '{"user_id": "U12345", "email": "foo@example.com" }'
$ curl http://localhost:5000/users/U12345
$ curl -X PATCH -H "Content-Type: application/json" http://localhost:5000/users/U12345 \
-d '{"email": "foo@example.com" }'
$ curl -X DELETE http://localhost:5000/users/U12345
リンク
- https://flask.palletsprojects.com/ (英語)
- https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/ (日本語)
main.py
templates