kuroの覚え書き

96の個人的覚え書き

肥大化するアプリを分割管理 Blueprint

データベースをあれこれ詰め込んでいき始めると@app.route()が増えていってややこしくなってくる。
そこでBlueprintの機能を用いて分割して管理していくことになる。

たとえばいまview.pyには

@app.route('/')
@app.route('/exome/', methods['GET', 'POST'])
@app.errorhandler(404)
@app.errorhandler(500)
@app.route('/entry/', methods=['GET', 'POST'])

の5つのルーティングが書かれており、それに付随するFormの定義があるわけだが、/exome/関連を別のスクリプトに移してしまいたい。
手順としては

  • まずexome.pyを作る。今回は新たなディレクトリは作らずview.pyと同階層(appの/からみて/app以下に作成した。
  • Blueprintをflaskからインポートするとともに、移動する部分に必要なものを一緒にインポートする。
  • app = Blueprint('exome', __name__)を追加。
from flask import Blueprint, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, RadioField, IntegerField, SubmitField
from app.models import User

app = Blueprint('exome', __name__)

class ExomeForm(FlaskForm):
    name = StringField('Sample name')
    aon1 = RadioField(choices=[('and', 'AND'), ('or', 'OR'), ('not', 'NOT')])
    gene = StringField('Gene')
    aon2 = RadioField(choices=[('and', 'AND'), ('or', 'OR'), ('not', 'NOT')])
    sex = SelectField('male or female', choices=[('', 'both'), ('male', 'male'), ('female', 'female')])
    aon3 = RadioField(choices=[('and', 'AND'), ('or', 'OR'), ('not', 'NOT')])
    score = IntegerField('score cut-off')
    submit = SubmitField('Search')

@app.route('/exome/', methods=['GET', 'POST'])
def exome_index():
    form = ExomeForm()
    user = User.query
    if form.name.data:
        user = user.filter(User.username==form.name.data)
    if form.sex.data:
        user = user.filter(User.sex==form.sex.data)
    if form.score.data:
        user = user.filter(User.score<=form.score.data)
    return render_template('/exome/index.html', form=form, contents=user, username=form.name.data, sex=form.sex.data, gene=form.gene.data, score=form.score.data, aon1=form.aon1.data, aon2=form.aon2.data, aon3=form.aon3.data)

そのうえで

  • view.pyからかぶる記述を取り除く。(インポートは他で必要な部分は残すこと)


これだけで完了である。

こうやって分割していけばview.pyにはエラーページとトップページ表示用のルーティングだけのこり、個別のページのコンテンツは別のスクリプトに分けて管理がしやすくなる。

各パートに必要な記述だけがまとまって記載されるので無駄な記述も減るし、間違いにも気が付きやすくなるというもの。せっかく動いていたのに、あとで別のページを付け足し編集するときに間違って必要な記述を消してしまったり、書き換えてしまうということがなくなると思われる。


ちなみにentryの方を別にわけると

from flask import render_template, session, redirect, url_for, Blueprint

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import Required
from app.models import User
from app import db

app = Blueprint('entry', __name__)

class NewNameForm(FlaskForm):
    name = StringField('Enter User name', validators=[Required()])
    submit = SubmitField('Submit')

@app.route('/entry/', methods=['GET', 'POST'])
def entry_index():
    form = NewNameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            session['known'] = False
        else:
            session['known'] = True
        session['name'] = form.name.data
        return redirect(url_for('entry.entry_index'))
    return render_template('/entry/index.html', form=form, name=session.get('name'),
                           known=session.get('known', False))

となる。注意する点は

        return redirect(url_for('entry.entry_index'))

という風にurl_for()の階層が一つ深くなっている点。


__init__.pyでは各種初期設定等の分離を、Blueprintではアプリの機能の分離を行って、アプリ構成をわかりやすく管理することができるのだな。
ただ、やり方は一つではないようで、先程view.pyに

  • from app import exomeを追加
  • app.register_blueprint(exome.app)を追記

としたが、これらはまるまる__init__.pyにおいても良い。



しっかし、Flask関連の情報は日本語ではあまり発信されていないものだな。Ruby on Railsが頑張っているせいか?
あくまで、自分用の作業日誌なわけだが、そこそこ検索で見に来る人がいるようだ。そこで、プログラマでもSEでもないのにwebアプリを作ることになったような、さまよえるpythonビギナーのお仲間(そんなのいるのか?)たちの一助になればいいなと思い、[Flask]タグをタイトルに付けることにした。ただし書いている本人も超ビギナーなので、嘘とは言わないが(書いたとおりに作って今のところ動いているわけだし)スマートでない方法をやっている可能性が大であることは申し添えておく。