Flask入門

記事タイトルとURLをコピーする

こんにちは。AWS CLIが好きな福島です。

今回は、インフラエンジニアのキャリアがメインの私が社内でPythonを使ったアプリ開発の学習をする機会があったため、学んだことをアウトプットしていきたいと思います。

アプリ開発では以下の技術に触れたため、いくつかブログを書ければと思っていますが、まずはFlask入門に関するブログを書くことにします。

  • Python
  • Flask
  • Jinja2
  • Bootstrap
  • Flask-SQLAlchemy

参考情報

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の違い

ここでflaskcookiessessionの違いに疑問が出てくると思います。 どちらもCookiesにデータが保存される点は同じなのですが、保存されているデータの暗号化有無に違いがあります。

numflaskcookiesで保存した値になりますが、 以下の通り、データの値がが暗号化されていないことが分かります。

sessionflasksessionで保存した値になりますが、 以下の通り、データの値が暗号化されていることが分かります。

ファイルのアップロード(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入門に関するブログをまとめてみました。 どなたかのお役に立てれば幸いです。

福島 和弥 (記事一覧)

2019/10 入社

AWS CLIが好きです。