はじめに
SQLAlchemyで一意制約違反した時に、以下のようなエラーが発生します。
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: user.email_address
一見、 IntegrityError
の例外をexceptで囲んで一意制約違反の例外として処理したくなります。
しかしこれはNGです。
なぜNGなのか
IntegrityError
は一意制約違反のエラーではなくDBのエラー全般をラップしたエラーだからです。
つまり一意制約違反以外にもNOT NULL制約違反や外部キー制約違反もIntegrityError
になります。
以下は悪い例です。
悪い例
def create_user(session, user_name, email):
try:
user = User(user_name=user_name, email=email)
session.add(user)
session.commit()
session.reflesh()
except IntegrityError:
session.rollback()
raise HTTPException(status=400, detail="email is already exist.")
emailの一意制約違反と決めてつけていますが、もしかすると別のカラムがNOT NULL制約に違反しているのかもしれません。
どうするべきか
IntegrityError
が保持しているオリジナルのエラーをexceptしてあげると良いです。
例えば以下のような感じです。(FastAPIとpsycopg2を例に用いています。)
crud.py
def create_user(session, user_name, email):
try:
user = User(user_name=user_name, email=email)
session.add(user)
session.commit()
session.reflesh()
except IntegrityError as sqlalchemy_error:
session.rollback()
raise sqlalchemy_error.orig # DatabaseごとのAPIのエラーをraiseする
main.py
from psycopg2 import errors as psycopg2_errors
@app.post("/users/")
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
try:
created_user = crud.create_user(
session=db,
user_name=user.user_name,
email=user.email
)
return created_user
except psycopg2_errors.UniqueViolation:
raise HTTPException(status=400, detail="email is already exist.")
except psycopg2_errors.NotNullViolation:
raise HTTPException(status=400, detail="invalid parameters.")