チュートリアル2です。
参考サイト: Flask – Tutorial(公式ドキュメント)
- チュートリアル
- クイックスタート
2. アプリケーションセットアップ
Flaskアプリケーションは、Flaskクラスのインスタンスです。設定やURLなどのアプリケーションに関わるあらゆることが、このクラスに登録されます。Flaskアプリケーションを生成する最も簡単な方法は、コードの最上層に直接、グローバルなFlaskインスタンスを作成することです。この方法はシンプルで使い勝手の良い方法ではありますが、プロジェクトの規模が大きくなるに連れて、対応しづらい問題を引き起こすことがあります。
グローバルにFlaskインスタンスを作成する代わりに、関数の内側で作ることもでき、この関数が「アプリケーションファクトリー(application factory)」と呼ばれるものです。設定や登録などのアプリケーションのセットアップに必要となる全てのことが関数の中で処理され、そして関数の戻り値としてアプリケーションが返されます。
2-1. Application Factory
ここからコーディングを開始します。まずflaskr
ディレクトリを作成し、そこに__init__.py
ファイルを作成します(ただし、このチュートリアルでは既にファイルを作成されたものとして扱い、作成するためのターミナルコマンド等は省略しています)。__init__.py
ファイルは次の2つの役割を持ちます。
- アプリケーションファクトリーを記述する
flaskr
ディレクトリををパッケージとして認識させる
# flaskr/__init__.py
import os
from flask import Flask
def create_app(test_config=None):
# インスタンス(app)の構築と設定の読み込み
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev',
DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
)
#テスト用設定を用いない場合(テストでない本番環境の場合)、存在すればインスタンスの設定を読み込み
if test_config is None:
app.config.from_pyfile('config.py', silent=True)
#テスト設定を用いる場合(つまりテストの時)
else:
app.config.from_mapping(test_config)
# instanceフォルダがあるか確認
try:
os.makedirs(app.instance_path)
except OSError:
pass
# "Hello, World!"を返すシンプルなページ
@app.route('/hello')
def hello():
return 'Hello, World!'
return app
create_app
はアプリケーションファクトリー関数です。今後この関数内にコードを追加していきますが、現時点での各コードの役割は下記になります。
app = Flask(__name__, instance_relative_config=True)
…Flaskインスタンスの作成__name__
…現在のpythonモジュールの名前。アプリケーションは、pathのセットアップのためにディレクトリやファイルの配置を把握する必要があるが、__name__
はそれを認識させる役割を持つinstance_relative_config=True
…アプリケーションの設定ファイルのpathがinstance
フォルダに対して相対pathであることを伝える。instance
フォルダはflaskr
ディレクトリの外側に置かれるが、厳秘の設定やデータベースファイルのような、バージョン管理の対象とならないローカルデータの保存場所として利用される
app.config.from_mapping()
…アプリケーションが利用する設定のデフォルト値をセットするSECRET_KEY
…データの安全性を保つために利用される。開発中の間は利便性の高いdev
をセットしているが、本番環境へのデプロイ時にはランダムな値で上書きするDATABASE
…SQLiteのデータベースファイルが保存される場所。instance
ディレクトリ内に設置される。次のセクションで詳しく扱う
app.config.from_pyfile()
…instance
フォルダ内にconfig.py
ファイルが存在する場合、デフォルトの設定値をそのファイルの値で上書きする。本番デプロイ時に本番用のSECRET_KEY
をセットする場合などに使われるtest_config
…アプリケーションファクトリーの引数で設定できるテスト用設定で、instance
フォルダでの設定の代わりに利用することができる。チュートリアルの後半で扱うテストで用いられるsilent=True
…引数のファイルが存在しない場合でもエラーが立たない
os.makedirs()
…app.instance_path
(instance
ディレクトリ)が存在するか確認する。Flaskは自動的にはinstance
ディレクトリを作らないが、チュートリアルではSQLiteデータベースファイルをそこに作るため、instance
ディレクトリが必要とされるため、この関数でinstance
フォルダの存在を確認している- @
app.route()
…アプリケーションが機能することを確認するシンプルなページの実装用に使われている。URL/hello
と関数との間のコネクションを確立し、この場合では文字列のレスポンスHello, World!
を返す。
2-2. アプリケーションを実行する
flask
コマンドを使って、アプリケーションを実行します。ターミナルを使って、Flaskインスタンスにアプリケーションの場所を伝え、開発者モードで実行させます。実行ディレクトリはflask-tutorial
であり、flaskr
パッケージの中ではありません。
開発者モードでの実行によって、例外・エラーが発生した際にインタラクティブデバッガーが発動され(画面にエラー内容が表示される)、また起動中にコードが変更されるとサーバがすぐにリスタートされます。これによって開発中にサーバを起動したままにしておくことができます。
# ターミナルで実行(Mac,Linux向け)
flask-tutorial$ export FLASK_APP=flaskr
flask-tutorial$ export FLASK_ENV=development
flask-tutorial$ flask run
* Serving Flask app "flaskr" (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 147-098-836
ターミナルでflask
コマンドを実行すると上記のような表示が出ます。ブラウザでhttp://127.0.0.1:5000/hello
にアクセスするとHello, World!
がブラウザに表示されます。
3. データベースのアクセスと定義
このアプリケーションでは、ユーザや投稿データを保存するためのデータベースにSQLiteを使用します。PythonはSQLiteをサポートする組み込みのsqliteモジュールをもちます。
SQLiteは別個にサーバを立てる必要がなく、Pythonにも組み込まれているため、非常に便利です。ただし、複数のリクエストで同時にデータベースに書き込もうとするとそれぞれの書き込みは順次処理されるため、時間がかかります。小さなアプリケーションの場合問題はありませんが、アプリケーションの規模が大きくなれば、他のデータベースに切り替えた方が良いです。
チュートリアルでは、SQLiteについて詳しい説明を行わないため、詳細はドキュメントを参照して下さい。
3-1. データベースへの接続
SQLite(や大多数のPythonデータベースライブラリ)を利用するためにまず最初にやることは、SQLiteデータベースとのコネクションを確立することです。あらゆるクエリや操作はコネクションを使って行われ、利用を終了すればコネクションを閉じます。
Webアプリケーションの中でこのコネクションは概ねリクエストに紐づけられます。リクエストを処理する時にコネクションを確立し、レスポンスが送られる際に閉じられます。
# flaskr/db.py
import sqlite3 #Pythonの標準モジュール
import click #Clickモジュールのインポート
from flask import current_app, g #flask関数のインポート
from flask.cli import with_appcontext #flask.cli関数のインポート
# DBとのコネクション確立
def get_db():
if 'db' not in g: # コネクションの確認(g.dbを持たなければコネクションがない)
g.db = sqlite3.connect( # Connectionオブジェクトの作成
current_app.config['DATABASE'], # DBの設定キー
detect_types=sqlite3.PARSE_DECLTYPES # 戻り値のカラムの型を読み取る
)
g.db.row_factory = sqlite3.Row
return g.db
# コネクションのクローズ
def close_db(e=None):
db = g.pop('db', None) #'db'を持てばConnectionオブジェクトを返し、持たなければ'None'を返す
if db is not None:
db.close()
g
は、それぞれのリクエストに対して一意である、特別なオブジェクトです。リクエストの際に複数の関数からアクセスされるデータを保存するために利用されます。コネクションはg
オブジェクトに保存されるため、同一のリクエスト間でget_db
関数が2度目の呼び出しがなされた場合、新しいコネクションを作る代わりにg
オブジェクトに格納されたコネクションが再利用されます。
current_app
は、リクエストを処理するFlaskアプリケーションを指定する、もう一つの特別なオブジェクトです。アプリケーションファクトリーを利用している場合、コードを書いている最中にはまだアプリケーション(FLaskインスタンス)が存在しません。get_db
はアプリケーション(Flaskインスタンス)が生成された時に呼び出され、リクエストを処理するためにcurrent_app
を利用します。
sqlite3.Row
はデータベースのレコードをdict型のような形式でアクセスできるようにします。これにより名前を指定することでカラムにアクセスできるようになります。
close_db
関数は、g.db
がセットされているかチェックすることによって、コネクションが作られているかチェックされます。コネクションが存在する場合は、そのコネクションが閉じられます。アプリケーションファクトリーの中でアプリケーションにclose_db
関数を伝えることで、それぞれのリクエストごとに呼び出されるようになります。
3-2. テーブルの作成
SQLiteの中で、データはテーブルとカラムに保存されます。これらはデータを保存し、抽出できるようになる前に作成しておく必要があります。Flaskrはユーザをuser
テーブルに保存し、投稿をpost
テーブルに保存します。空のテーブルを作るため、SQLコマンドを使ってファイルを作成します。
# flaskr/schema.sql
DROP TABLE IF EXISTS user; #テーブルが存在していればそのテーブルを削除
DROP TABLE IF EXISTS post;
# 'user'テーブルの作成
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT, # データ型:INTEGER、入力がない場合は今までの最大値+1を入力
username TEXT UNIQUE NOT NULL, # 'NULL'は入力できない
password TEXT NOT NULL
);
CREATE TABLE post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, # 入力がない場合は、追加時点のタムスタンプを入力
title TEXT NOT NULL,
body TEXT NOT NULL,
FOREIGN KEY (author_id) REFERENCES user (id) # 'author_id'は'user'テーブルの'id'の値しか格納できな
);
これらのSQLコマンドを走らせる関数をdb.py
に追加します。
# flaskr.db.py
def init_db():
db = get_db() # コネクションの接続
# f(schema.sql)読込('r'モード) → 'utf-8'でデコード → dbに書き込み
with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8'))
@click.command('init-db')
@with_appcontext
def init_db_command():
# 既存のデータを削除し、新しいテーブルを作る
init_db()
click.echo('Initialized the database.')
open_resource()
はflaskrからの相対pathで指定されたファイルを開きます。これにより、アプリケーションをデプロイする時にファイルの絶対pathを把握する必要がなくなるためとても便利です。get_db
はデータベースのコネクションを返し、そのファイルから読み取られたコマンドを実行するために利用されます。
click.command()
は、init_db
関数を呼び出すコマンドラインのコマンドinit-db
を定義し、成功メッセージを表示します。コマンドの記載方法についてはCommand Line Interfaceを参照下さい。
(作成されるテーブルのイメージ)
3-3. アプリケーションへの登録
close_db
関数とinit_db_command
関数は、アプリケーションインスタンス(Flaskインスタンス,app)に登録される必要があります。登録しないと、アプリケーション内で使用することができません。しかし、このアプリケーションではアプリケーションファクトリーを利用しているので、関数を書いている時点でアプリケーションインスタンスは利用することはできません。そのため、代わりにアプリケーション(Flaskインスタンス,app)を引数にとる関数を定義し、それをアプリケーションファクトリーに登録します。
# flaskr/db.py
def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)
app.teardown_appcontext()
は、レスポンスを返した後の処理として引数の関数を呼び出すよう設定する関数です.
app.cli.add_command()
は、flask
コマンドで呼び出せる新しいコマンドを加えます。
db
モジュールをインポートし、アプリケーションファクトリーにinit_db
関数を登録します。
# flaskr/__init__.py
def create_app():
app = ...
# existing code omitted
from . import db
db.init_app(app)
return app
3-4. データベースの初期化
これでinit-db
コマンドがアプリケーションに登録されたので、run
コマンド同様にflask
コマンドを使って呼び出すことができます。
※ 注記
サーバを起動し続けている場合、init-db
コマンドはサーバを止めて実行することも、別のターミナルを開いてそこで実行することもできます。新しいターミナルで実行する場合、プロジェクトディレクトリに移動し、仮想環境をアクティベートした上で実行する必要があります。また、同様に環境変数FLASK_APP
とFLASK_ENV
を最初に指定する必要があります。
init_db
コマンドを実行します。
(env)flask-tutorial$ flask init-db
Initialized the database.
これでinstance
フォルダ内にflaskr.sqlite
ファイルが作成されます。