こんにちは。AWS CLIが好きな福島です。
今回は、インフラエンジニアのキャリアがメインの私が社内でPythonを使ったアプリ開発の学習をする機会があったため、学んだことをアウトプットしていきたいと思います。
アプリ開発では以下の技術に触れたため、いくつかブログを書ければと思っていますが、まずはFlask入門に関するブログを書くことにします。
- Python
- Flask
- Jinja2
- Bootstrap
- Flask-SQLAlchemy
- 参考情報
- Flaskとは
- Flaskを利用する前に
- WSGIができた背景
- 触ってみる
- 複数のURLパスを実装する
- URLのパスパラメータに応じて表示する内容を変更する
- URLのクエリパラメーターを扱う(request.args)
- リダイレクト(redirect)
- URLを指定する場合の推奨方法(url_for)
- HTTPメソッドの制御
- リクエストのformのデータを扱う(request.form)
- Cookiesを扱う(request.cookies, make_response)
- クライアントサイドでセッション管理(session)
- CookiesとSessionの違い
- ファイルのアップロード(request.files)
- 終わりに
参考情報
Flaskとは
一言で言うと、Webアプリのフレームワークとなります。
フレームワークなので、効率的にWebアプリを開発できるライブラリっていうことですね。
Flaskを利用する前に
Flaskを利用する前に押さえておきたい概念として、Webサーバ
、Webアプリケーション
、WSGI
を説明します。各要素の概念図は以下のイメージです。
- Webサーバ
- ユーザーからのHTTPリクエストを受信し、HTTPレスポンスを返す機能を持つソフトウェアです。
- Webアプリケーション
- ユーザーからのHTTPリクエストに応じて動的なレスポンスを生成する機能を持つソフトウェアです。
- WSGI(Web Server Gateway Interface)
- WebサーバとWebアプリを繋ぐためのインターフェイス定義です。WebサーバはWSGIを通じてWebアプリケーションを呼び出し、アプリケーションはWSGIを通じてレスポンスをWebサーバに返します。
この要素において、FlaskはWebアプリケーションを担うソフトウェアとなります。
ポイント
実際にFlaskを利用すると、flask run
というコマンドでWebアプリを実装できるため、
FlaskはWebサーバの機能も持っていると思ってしまいます。
あながち間違いではないのですが、厳密には、flaskに組み込まれているWerkzeug
がWebサーバの機能を担っています。
そして、Werkzeug
は開発向けのWebサーバとなるため、本番利用にはその他のWebサーバを使う必要があります。この辺りの詳細はまた別のブログでご紹介できればと思います。
WSGIができた背景
Wikipediaに分かりやすく解説されていたので、引用します。
過去において、Pythonに多種のWebアプリケーションフレームワークが存在することは、PythonでWebアプリケーションを開発しようとする者にとって問題になっていた。というのも、Webアプリケーションフレームワークを選択することによって、使用できるWebサーバが制限されてしまったり、その逆の制限が発生したりしたためである。 中略 この問題を解決するためにWSGIが考案された。WSGIは、Pythonにおける、WebアプリケーションとWebサーバを接続する標準仕様を定めるものである。これによって、WSGIに対応したWebアプリケーション(やフレームワーク)は、WSGIに対応した任意のWebサーバ上で運用できるようになる。
https://ja.wikipedia.org/wiki/Web_Server_Gateway_Interface
要は、WebサーバとWebアプリケーションを繋ぐ共通のインターフェイス定義がなかった際に、互換性のあるWebサーバとWebアプリケーションのセットが制限され、開発する上で問題になったということですね。
余談
余談になりますが、WSGIの後継となるASGI(Asynchronous Server Gateway Interface)
というインターフェイス定義もあります。
WSGIにサポートしているWebサーバ、Webアプリケーション、ASGIにサポートしているWebサーバ、Webアプリケーションは、それぞれ異なります。
その上でFlaskはWSGIにサポートしているWebアプリケーションなんだなと理解しておくと良いと思います。
触ってみる
前置きが長くなってしまいましたが、ここからFlaskを触ってみます。 まずは、以下のコマンドでflaskをインストールします。
pip install flask
app.py
というファイルに以下のコードを書きます。
from flask import Flask app = Flask(__name__) @app.route('/') def index(): return 'Index Page'
flask run --debug
のコマンドを実行します。
debug
オプションをつけることでファイルを更新すると自動で再読み込みが走ってくれます。
先に記載しておくと、この後、色々なコードを紹介しますが、コードを実際に動かしたい場合は、app.py
を上書きすることで動作確認ができます。
$ flask run --debug * Debug mode: on WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000 Press CTRL+C to quit * Restarting with stat * Debugger is active! * Debugger PIN: 114-794-361
そして、http://127.0.0.1:5000
にアクセスすると以下の画面が表示されます。
直感的に分かると思いますが、デコレーターである@app.route("/")
によって
URLのパスとPythonの関数(サンプルコードの場合、index)が紐づけられています。
そのため、/
にアクセスすると、Index Page
が表示されます。
複数のURLパスを実装する
つまり、以下のようにコードを書くことでURLパスごとに表示する内容を変えられます。
from flask import Flask app = Flask(__name__) @app.route('/') def index(): return 'Index Page' @app.route('/hello') def hello(): return 'Hello, World'
URLのパスパラメータに応じて表示する内容を変更する
基本的な使い方
以下のようなコードを書くことで、URLのパスパラメータに応じて表示する内容を変更することも可能です。
from flask import Flask app = Flask(__name__) @app.route("/user/<username>") def show_user_profile(username): return f"User {username}"
上記は分かりやすいように省いたのですが、 実際に実装する場合は、URLのパスパラメーターに悪意のある文字列が含まれる可能性があるため、 以下のようにエスケープ処理を実装する必要があります。
from flask import Flask from markupsafe import escape app = Flask(__name__) @app.route("/user/<username>") def show_user_profile(username): return f"User {escape(username)}"
この後もエスケープ処理は省いて記載しますが、実際にはエスケープ処理をする必要がある点は注意してください。
データ型の指定
<>
の部分は、<converter:variable_name>
のように書くことができます。
variable_nameは任意の値を設定することができ、converterには以下の値を設定できます。
データ型 | 説明 |
---|---|
string | (標準設定)スラッシュ(/)以外の全てのテキストを受け付けます |
int | 正の整数を受け付けます |
float | 正の浮動小数点の値を受け付けます |
path | stringに似ていますが、スラッシュ(/)を受け付けます |
uuid | UUID文字列を受け付けます |
int
以下のように書くとURLパスパラメータには、正の整数のみ受け取ります。
正の整数以外を入力すると、Not Found
になります。
from flask import Flask app = Flask(__name__) @app.route('/post/<int:post_id>') def show_post(post_id): return f'Post {post_id}'
path
以下のように書くとURLパスパラメータに、/
も含めた文字列を受け取ります。
from flask import Flask app = Flask(__name__) @app.route("/path/<path:subpath>") def show_subpath(subpath): return f"Subpath {subpath}"
URLのクエリパラメーターを扱う(request.args)
request
モジュールを利用することでクエリパラメータを扱うことができます。
from flask import Flask, request app = Flask(__name__) @app.route("/") def index(): query_param = request.args return query_param.get("name", "default")
name
というクエリパラメーターを含めない場合、default
が表示されます。
name
というクエリパラメーターを含めた場合、name
の値が表示されます。
リダイレクト(redirect)
redirect
モジュールを利用することでリダイレクトすることも可能です。
例えば、/
にアクセスした際に、/login
にリダイレクトする場合は以下のように記載します。
from flask import Flask, redirect app = Flask(__name__) @app.route("/") def index(): return redirect("/login") @app.route("/login") def login(): return "Login Page"
画像ではリダイレクトされていることをお伝えできないですが、
以下は/
にアクセスした際の結果です。/login
にリダイレクトされています。
URLを指定する場合の推奨方法(url_for)
先ほど、リダイレクトする際に以下のようにURLのパスを記載しました。
return redirect("/login")
Flaskでは、このような場合にURIのパスを記載するのではなく、url_for関数を利用することが推奨されています。
このurl_for関数は、url_for("login")
と記載することができ、
要は関数名から関数に紐づくURLパスを生成することができます。
return redirect(url_for("login"))
全プログラムは以下の通りです。
from flask import Flask, redirect, url_for app = Flask(__name__) @app.route("/") def index(): return redirect(url_for("login")) @app.route("/login") def login(): return "Login Page"
なぜ、この書き方が良いかについては、以下を確認ください。
https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/quickstart.html#url-building
一応、私なりの解釈を記載します。
例えば、URLパスと関数名に変更があった際に URLパスの変更は、Webアプリの利用者に影響がある部分ですが、 関数名の影響範囲は開発者のみになります。
そうなった場合、URLパスと関数名、どちらの可変性を上げたいかというと、URLパスだと思います。url_forを使うことを徹底していれば、URLパスを変更したくなった場合、@app.route()
の部分を変更するだけで良いため、url_forを使うことが推奨されているのだと思います。
HTTPメソッドの制御
リクエストのHTTPメソッドを絞ることもできます。
以下は、/
に対してPOST
メソッドのみ受け付ける設定になります。
デフォルトでは、GET
メソッドのみを受け付ける設定になります。
from flask import Flask app = Flask(__name__) @app.route("/", methods=["POST"]) def index(): return "Post Method"
GET
メソッドでアクセスした場合、405
エラーが表示されます。
$ curl -X GET http://localhost:5000 <!doctype html> <html lang=en> <title>405 Method Not Allowed</title> <h1>Method Not Allowed</h1> <p>The method is not allowed for the requested URL.</p> $
POST
メソッドでアクセスした場合、Post Method
というメッセージが正しく表示されます。
$ curl -X POST http://localhost:5000 Post Method $
また、request
モジュールを利用することで1つの関数でメソッドに応じて処理を変更することもできます。
from flask import Flask, request app = Flask(__name__) @app.route("/", methods=["GET", "POST"]) def login(): if request.method == "POST": return "Post Method" else: return "Get Method"
GET
メソッドでアクセスした場合、Get Method
が表示されます。
$ curl -X GET http://localhost:5000 Get Method $
POST
メソッドでアクセスした場合、Post Method
が表示されます。
$ curl -X POST http://localhost:5000 Post Method $
リクエストのformのデータを扱う(request.form)
request
モジュールにより、POST
またはPUT
で送信されるformのデータを扱うこともできます。
from flask import Flask, request app = Flask(__name__) @app.route("/", methods=["POST"]) def index(): form = request.form return form.get("name", "default")
formのデータなしでリクエストした場合、default
と表示されます。
$ curl -X POST http://localhost:5000 default $
formのデータにname=test
を含めてリクエストした場合、test
と表示されます。
$ curl -X POST http://localhost:5000 -d 'name=test' test $
Cookiesを扱う(request.cookies, make_response)
request.cookies
を使うことでリクエストのCookiesを扱うことができます。
また、make_response
を使うことでレスポンスを生成し、set.cookieにより、レスポンスにCookiesを含めることができます。
from flask import Flask, make_response, request app = Flask(__name__) @app.route("/") def index(): cookie_num = request.cookies.get("num", 0) num = int(cookie_num) + 1 resp = make_response(f"Count: {num}") resp.set_cookie("num", str(num)) return resp
これによりアクセス回数をカウントすることができます。
リロードすると、カウントが2になります。
クライアントサイドでセッション管理(session)
session
を利用することでセッション管理もできます。
flask
に含まれるsession
は、クライアントサイドでセッションを管理します。
補足ですが、サーバサイドでセッションを管理したい場合は、拡張機能のFlask-Session
を利用します。今回は紹介しないのですが、興味がある方は以下をご確認ください。
import secrets from flask import Flask, session app = Flask(__name__) app.secret_key = secrets.token_hex() @app.route("/") def index(): session_num = session.get("num", 0) num = int(session_num) + 1 session["num"] = num return f"Count(Session): {num}"
これによりアクセス回数をカウントすることができます。
リロードすると、カウントが2になります。
CookiesとSessionの違い
ここでflask
のcookies
とsession
の違いに疑問が出てくると思います。
どちらもCookiesにデータが保存される点は同じなのですが、保存されているデータの暗号化有無に違いがあります。
num
がflask
のcookies
で保存した値になりますが、
以下の通り、データの値がが暗号化されていないことが分かります。
session
がflask
のsession
で保存した値になりますが、
以下の通り、データの値が暗号化されていることが分かります。
ファイルのアップロード(request.files)
request.files
を利用することでアップロードされたファイルを扱うこともできます。
以下は、/
にアクセスした際に、ファイルをアップロードできる画面をレスポンスします。
/
の画面からファイルがアップロードされた場合、ファイルをサーバに保存します。
from flask import Flask, redirect, request, url_for app = Flask(__name__) @app.route("/") def upload_form(): return """ <h1>Upload new File</h1> <form method=post action="/upload" enctype=multipart/form-data> <input type=file name=file> <input type=submit value=Upload> </form> """ @app.route("/upload", methods=["POST"]) def upload_file(): if file := request.files["file"]: file.save(f"{file.filename}") return redirect(url_for("upload_form"))
/
にアクセスすると、ファイルをアップロードする画面が表示されます。
適当にアップロードしたファイルを選択し、upload
ボタンを押下します。
upload
ボタンが押下されると、少し分かりづらいですが、選択したファイルがリセットされていることが分かります。
flaskを起動しているカレントディレクトリを確認するとファイルがアップロードされていることが分かります。
$ ls -lrt アップロードテスト.txt -rw-r--r-- 1 kazuya staff 0 7 28 16:23 アップロードテスト.txt $
終わりに
今回は、Flask入門に関するブログをまとめてみました。 どなたかのお役に立てれば幸いです。