下町エンジニアの雑多ブログ

東京の端っこでITエンジニアやってます。テックブログを中心に書いていきたいと思います

Flask入門~公式ドキュメントを読み解く~ Day 3 ~

f:id:usktkt:20181019225022p:plain

前回の内容

前回の記事では、チュートリアルの完成像を確認し、アプリケーションのセットアップを行いました。

今回は、データベース周りのあれこれをやっていきます。

データベース接続

早速、データベース接続と切断のためのメソッドを作成していきます。flaskrディレクトリ配下に、db.pyというファイルを作成し、以下を入力してください。このファイルにより、__init__.pyで設定したDATABASEへの接続と、切断ができるようになります。

import sqlite3
import click
from flask import current_app, g
from flask.cli import with_appcontext

def get_db():
    if 'db' not in g:
        g.db = sqlite3.connect(
            current_app.config['DATABASE'],
            detect_types=sqlite3.PARSE_DECLTYPES
        )
        g.db.row_factory = sqlite3.Row

    return g.db

def close_db(e=None):
    db = g.pop('db', None)

    if db is not None:
        db.close()

gはリクエスト毎に生成されるユニークなオブジェクトとなります。つまりリクエストの中で初めてDB接続要求があった場合、新たにDBコネクションを生成し、同じリクエストの中で複数回接続要求があった場合、最初に生成したオブジェクトを使いまわします。

current_app.config['DATABASE']は現在実行しているFlaskアプリケーションの設定値'DATABASE'を取り出します。 detect_typesをsqlite3.PARSE_DECLTYPESとしておくことで、戻り値のカラムの型を読み取ることが出来るようになります。 *1

sqlite3.connectによって、current_app.config['DATABASE']で取得した、'DATABASE'キーに設定されたデータベースに接続します。

row_factoryにsqlite3.Rowをセットしておくことで、クエリの結果にカラム名でアクセス出来るようになります。

最後のclose_dbメソッドでは、gオブジェクトから'db'を取り出し、それが空でなければ(接続があれば)、切断します。

テーブル作成

次にテーブルを生成するSQLを作成します。flaskrディレクトリ配下にschema.sqlを作成し、以下を入力してください。なお、SQL文については今回は詳細に解説しないので、不明点がある方は、入門書などで調べてみてください。

DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS post;

CREATE TABLE user (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT UNIQUE NOT 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)
);

このSQLファイルを起動するためのメソッドを、db.pyに追加します。db.pyの最終行に以下を追記してください。

def init_db():
    db = get_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ディレクトリ内のファイルを開きます。そして次の行で開いたファイルをutf-8でデコードして読み込み実行します。

clickモジュールをインポートすることで、独自のflaskコマンドを作成することが出来ます。@click.command('init-db')によって'init-db'コマンドを定義し、@with_appcontextでflaskrアプリケーションと紐づけています。

アプリケーションへの登録

続いてclose_dbメソッドとinit_db_commandメソッドをアプリケーションに登録します。db.pyの最終行に以下を追加してください。

def init_app(app):
    app.teardown_appcontext(close_db)
    app.cli.add_command(init_db_command)

teardown_appcontextメソッドは、レスポンスを返した後に自動的に引数のメソッドを呼び出します。

app.cli.add_commandにinit_db_commandをセットすることで、flaskコマンドから呼び出すことが出来るようになります。

このinit_appメソッドを__init__.pyモジュールにインポートします。__init__.pyの最終行にあるreturn appの上の行に以下を追記してください。追記するコードは、return appとインデントが等しいことに注意してください。

    from . import db
    db.init_app(app)

    return app  # 最終行

データベースの初期化

それでは最後にデータベースを初期化しましょう。前回起動したサーバーが立ち上がったままの場合、control + Cで一度サーバーを止めましょう。 データベースの初期化は以下のコマンドで実行できます。

flask init-db

このコマンドを打ち込んだ際の挙動を示すと以下のような順で処理が進みます。

  1. __init__.pyが起動し、create_appメソッドが呼び出される。
  2. db.pyモジュールのinit_appメソッドが呼び出される。
  3. __init__.pyの処理が終了後、'init-db'コマンドと紐づけられたinit_db_commandが呼び出される
  4. init_db_commandメソッドからinit_dbメソッドが呼び出され、データベースの接続と、schema.sqlの実行が行われる。
  5. init_dbメソッドの処理が終了すると、文字列'Initialized the database.'が出力され、DB接続を切断し終了。

処理が終わった後に、instanceディレクトリをのぞいてみると、flaskr.sqliteファイルが出力されているはずです。

まとめ

今回はデータベースの接続・切断から、データベースの初期化まで行いました。clickでflaskコマンドを作成するあたりは少し難しいかもしれません。

次回は機能ごとにファイルの分割を可能にするBlueprintについてみていきます。

内容について、質問・指摘があればコメントよろしくお願いします。