Django
と、シンプル・軽量な Flask
として使い分けられています。
Flask
に関連して Werkzeug
(WSGIライブラリ)、Jinja2
(テンプレートエンジン)なども開発しています。
Jinja2
も、テンプレート → テンプル → 神社 から命名されています。日本好きっぽいですね。
下記の環境を想定しています。
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
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()
で、どの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)
@app.route()
で指定するURL末尾にスラッシュをつけると /foo
でアクセスしても /foo/
にリダイレクトされて、スラッシュ有りでも無しでもアクセスできますが、つけない場合はスラッシュ無しでしかアクセスできません。
@app.route("/foo/") # /foo でも /foo/ でもアクセスできる @app.route("/baa") # /baa はアクセス可。/baa/ はアクセス不可
メソッドの中では 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
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 を読み取るには 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_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データを受け取るには、クライアントから 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
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