check_password_hash関数の箇所でなぜかエラーになり解決に時間を要しました。
忘れないうちに、備忘として残しておこうと思います。
環境
docker 24.0.6
docker-compose 2.23.0
python 3.13.0
Flask 3.1.0
nginx 1.27.4
gunicorn 23.0.0
PostgreSQL 17.3
環境はdockerを用いてnginxのwebサーバーコンテナ、gunicorn+flaskのアプリケーションサーバーコンテナ、PostgreSQLのDBコンテナを構築しています。
こちらの記事を参考にしました。
ソースコード
@login_bp.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm(request.form)
if request.method == "GET":
return render_template("user_account/login.html", form=form)
elif request.method == "POST" and form.validate_on_submit():
user = User.select_by_emial(form.email.data)
if user and user.check_password(form.password.data):
login_user(user, remember=True, duration=timedelta(seconds=600))
flash("ログインしました")
return redirect(url_for("user.user"))
elif user == None:
flash("ユーザーが存在しません")
return render_template("user_account/login.html", form=form)
class User(UserMixin, db.Model):
__tablename__ = "users"
id = Column("id", Integer, primary_key=True, autoincrement=True)
username = Column("username", String(255), nullable=False)
email = Column("email", String(255), nullable=False, unique=True)
age = Column("age", Integer)
password = Column("password", String(255), nullable=False)
(中略)
@classmethod
def check_password(self, password):
return check_password_hash(self.password, password)
原因となった箇所のみ抜粋です。
具体的にはmodels.py内の一番最後の行のcheck_password_hash(self.password, password)でエラーが起きました。
事象
3.check_password_hash関数でAttributeErrorが発生しました。なんで?
調査
エラーはAttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with User.password has an attribute 'split'と吐いています。要するに、ユーザーパスワードに関連付けられたオブジェクトがsplitメソッドを持ってないよってことです。
check_password_hash関数の中身では、引数で渡されてきたpwhashに対してsplitを行っています。splitが使えるということはpwhashはおそらく文字列ですね。
該当箇所付近をデバッグしてみます。
printでそれぞれの値と型を確認します。
まずはviews.pyから。
@login_bp.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm(request.form)
if request.method == "GET":
return render_template("user_account/login.html", form=form)
elif request.method == "POST" and form.validate_on_submit():
user = User.select_by_emial(form.email.data)
print(type(user.password))#user.passwordの型
print(user.password)#user.passwordの値(ハッシュ化されたデータ)
print(type(form.password.data))#form.password.dataの型
print(form.password.data)#form.password.dataの値
if user and user.check_password(form.password.data):
login_user(user, remember=True, duration=timedelta(seconds=600))
flash("ログインしました")
return redirect(url_for("user.user"))
elif user == None:
flash("ユーザーが存在しません")
return render_template("user_account/login.html", form=form)
return render_template("user_account/login.html", form=form)
ターミナルに出力された値を確認するとどちらもstring(文字列型)です。
user.passwordもハッシュ化されたデータが入ってますし、form.password.dataも入力した'password'という文字列が入っています。
どちらも問題ありません。
次に、models.pyのcheck_password_hash付近をデバッグします。
コードとデバッグ結果がこちらです。
(中略)
@classmethod
def check_password(self, password):
print(type(self.password))
print(self.password)
print(type(password)) #引数として渡されてきたパスワードの型
print(password) #引数として渡されてきたパスワードの値
return check_password_hash(self.password, password)
引数で渡されてきたパスワードの方は型も値も一致してるけど、self.passwordの方はインスタンス変数password取得できてなくない???
原因特定
小一時間悩みましたが、check_password_hash関数にデコレータの@classmethodがついた状態なので、クラスメソッドとしての振る舞いになっていたことが原因でした。多分ほかの関数からコピペしてきたときに一緒に持ってきちゃって消し忘れたのだと思います。普通に見落としです...
(中略)
- @classmethod
def check_password(self, password):
return check_password_hash(self.password, password)
@classmethodをコメントアウトして実行すると、クラスではなくインスタンスとして機能して想定通りの結果になりました!!!
HAPPY