【Python&Flask】パスワード管理アプリを作ってみる③【デコレーターと設定ファイル編】

技術

今回は画面は作らないけど重要!!

第2回から少し時間が開いてしまいましたが、久しぶりにPython&Flaskでパスワード管理アプリを作る企画の続きをやっていきたいと思います。
過去2回の記事はこちらからどうぞ。

前回までで、ログイン機能・データを表示する機能ができました。
順番的には次はデータを登録する機能を作りたいところです。

ですが、その前に一度立ち止まって、ここまでのプログラムを見直してみましょう。

ということで今回は前回までに作成したプログラムを少し整理しようと思います。

デコレーターって?

前回作成した「indexview.py」のコードを再掲します。

import psycopg2
from flask import render_template, redirect, session, jsonify
from flask.views import MethodView
from models.secretdata import SecretData

class IndexView(MethodView):
    def get(self):
        if "login" in session and session["login"] == True:
            with psycopg2.connect("postgresql://dev:pass@db:5432/postgres") as db:
                data = SecretData.all(db)
            return render_template("index.html", title="トップ", data = data)
        else:
            return redirect("/login")

    @staticmethod
    def get_password(id: int):
        if "login" in session and session["login"] == True:
            with psycopg2.connect("postgresql://dev:pass@db:5432/postgres") as db:
                data = SecretData.get(db, id)
            if data == None:
                return "", 404
            else:
                return jsonify({"password": data.password})
        else:
            return "", 401

このプログラムにはgetget_passwordという2つのメソッドが実装されていますが、ほとんど同じ実装になっている部分があります。

  1. ログイン済みかどうか判定する部分
  2. DB接続する部分

の2か所です。
具体的に言うとこの部分になります。

# 1. ログイン済みかどうか判定する部分
if "login" in session and session["login"] == True:
    ・・・
else:
    ・・・

# 2. DB接続する部分
with psycopg2.connect("postgresql://dev:pass@db:5432/postgres") as db:
    ・・・

今後、viewクラスの実装が増えていくことを考えた時、同じコードを毎回コピー&ペーストするより使い回しのできる部品として用意するほうが便利です。

ということで、「1. ログイン済みかどうか判定する部分」から部品化しましょう。
Python のデコレーターという機能を使います。

デコレーターというのは関数の前につける「@・・・」という表現のやつです。
先ほどみた「indexview.py」の中にも下記のようなコードがありましたね。

@staticmethod
def get_password(id: int):
    ・・・

この”@staticmethod”の部分がデコレーターです。

デコレーターが何をするものなのかは、ログイン済みかどうかを判定するデコレーターの実装を見てもらえばわかると思います。

def login_required(return_json):
    def _login_required(func):
        def wrapper(*args, **keywords):
            if "login" in session and session["login"] == True:
                # ログイン済みの場合
                result = func(*args, **keywords)
            else:
                # ログインしていない場合
                if return_json:
                    result = ("", 401)
                else:
                    result = redirect("/login")
            return result
        return wrapper
    return _login_required

関数が入れ子になったような構造で定義されています。
login_required → _login_required → wrapper の順に入れ子になっていて、wrapperの中の実装がデコレーターとしてのメインの実装になっています。

下記はwrapperの中の実装を抜き出したものです。

if "login" in session and session["login"] == True:
    # ログイン済みの場合
    result = func(*args, **keywords)
else:
    # ログインしていない場合
    if return_json:
        result = ("", 401)
    else:
        result = redirect("/login")
return result

ログイン判定の部分は「indexview.py」と同じですね。

result = func(*args, **keywords)

この部分でデコレーターが装飾している関数の本体部分が呼び出されます。

ログインできていない場合はちょっと特殊です。
関数の本体部分は実行せずに、処理を終了しています。
戻り値にredirect("/login")の戻り値をセットすることで、ログイン画面へのリダイレクト(ステータス302)をクライアントに返します。
ちなみに非同期のリクエスト用に401エラーが返せるパターンも実装しています。

見てもらえばわかるように、処理の前後に処理を差し込めるようになるのがデコレーターという機能です。

では作成した login_required デコレーターを使ってログインチェックをするように「indexview.py」を修正しましょう。

import psycopg2
from flask import render_template, jsonify
from flask.views import MethodView
from decorators.loginrequired import login_required
from models.secretdata import SecretData

class IndexView(MethodView):
    @login_required(return_json=False)
    def get(self):
        with psycopg2.connect("postgresql://dev:pass@db:5432/postgres") as db:
            data = SecretData.all(db)
        return render_template("index.html", title="トップ", data = data)

    @staticmethod
    @login_required(return_json=True)
    def get_password(id: int):
        with psycopg2.connect("postgresql://dev:pass@db:5432/postgres") as db:
            data = SecretData.get(db, id)
        if data == None:
            return "", 404
        else:
            return jsonify({"password": data.password})

ログインチェックするかどうかを「@login_required」デコレーターの有無だけで表現できるので随分すっきりしたと思います。

Configparserで設定ファイルを管理する

続いて、DB接続する部分を修正しましょう。

現在の実装では同じ接続文字列「postgresql://dev:pass@db:5432/postgres」がgetメソッドとget_passwordメソッドの両方に登場しています。

今は開発用のDBに接続していますが、本番稼働やテスト環境では接続先を切り替える必要があります。
そうなった時、今のままではいろいろなところにある接続文字列を1つ1つ修正して回らないといけません。

それでは不便なのでこの接続文字列の定義を設定ファイルに移動して、各プログラムはその設定を参照するようにしたいです。
そのために今回はconfigparserというモジュールを使います。

まずはindexview.pyと同じディレクトリに「config.ini」ファイルを作ります。

[DB]
connectionstring = postgresql://dev:pass@db:5432/postgres

[DB]セクションの[connectionstring]というパラメータキーで接続文字列を定義しました。

この設定ファイルを configparser で読み込みます。

import configparser

config = configparser.ConfigParser()
config.read("config.ini")
print(config["DB"]["connectionstring"])

ConfigParserを生成して、readメソッドで「config.ini」ファイルを読み込んでいます。
設定を参照する時はconfig[セクション名][パラメータキー]で取得できます。

この実装を各Viewクラスに実装してもいいんですが、せっかくなので設定を管理するクラスを作ってみようと思います。

from configparser import ConfigParser

class Config:
    config = ConfigParser()
    config.read("config.ini")

    @staticmethod
    def connectionstring():
        return Config.config["DB"]["connectionstring"]

Configクラスはstaticな connectionstringという名前のメソッドを持つようにしました。
このConfigクラスを使って接続文字列を取得するように indexview.py を修正します。

import psycopg2
from flask import render_template, jsonify
from flask.views import MethodView
from config import Config
from decorators.loginrequired import login_required
from models.secretdata import SecretData

class IndexView(MethodView):
    @login_required(return_json=False)
    def get(self):
        with psycopg2.connect(Config.connectionstring()) as db:
            data = SecretData.all(db)
        return render_template("index.html", title="トップ", data = data)

    @staticmethod
    @login_required(return_json=True)
    def get_password(id: int):
        with psycopg2.connect(Config.connectionstring()) as db:
            data = SecretData.get(db, id)
        if data == None:
            return "", 404
        else:
            return jsonify({"password": data.password})

接続文字列は config.ini で管理されているので、本番用のDBへ切り替える時もconfig.iniを編集するだけでOKになりました。

まとめ

今回はデコレーターという機能とconfigparserというモジュールを導入してみました。

ログインチェックにしても、設定ファイルの管理方法にしても、今回紹介した方法だけが正解ってわけではありません。
Flaskには便利なモジュールがたくさん用意されていて、様々な機能を簡単に導入できるようになっています。

実はログインチェックもモジュールが用意されていますし、設定ファイルの管理にも別のモジュールが用意されています。
それらを使った方法も、いずれ試してみたいと思います。

最後に、今回作成した機能を導入した状態のフォルダ構成を紹介しておきます。

次回はいよいよデータ登録の機能の実装を進めます。
よかったら是非次回の記事も見てください。

コメント

タイトルとURLをコピーしました