エラーをキャッチしようとして問題が発生しました。 Pyramid/SQLAlchemyを使用しており、主キーとして電子メールを使用してサインアップフォームを作成しました。問題は、重複する電子メールが入力されると、IntegrityErrorが発生するため、そのエラーをキャッチしてメッセージを提供しようとしていますが、何をしてもキャッチできなくても、エラーが表示され続けます。
try:
new_user = Users(email, firstname, lastname, password)
DBSession.add(new_user)
return HTTPFound(location = request.route_url('new'))
except IntegrityError:
message1 = "Yikes! Your email already exists in our system. Did you forget your password?"
except exc.SQLAlchemyError
を試したときに同じメッセージが表示されます(ただし、包括的にすべてをキャッチするのではなく、特定のエラーをキャッチしたいのですが)。 exc.IntegrityError
も試しましたが、うまくいきませんでした(APIには存在しますが)。
私のPython構文に何か問題がありますか、それともそれをキャッチするためにSQLAlchemyで特別なことをする必要がありますか?
この問題を解決する方法はわかりませんが、問題の原因についていくつか考えています。 SQLAlchemyが例外自体を発生させ、Pyramidがビューを生成しているため、except IntegrityError:
がアクティブ化されないため、tryステートメントは失敗していませんが成功している可能性があります。または、おそらく、このエラーを完全に間違ってキャッチしています。
Pyramidでは、ZopeTransactionExtension
を使用するようにセッション(scaffoldが自動的に行う)を構成した場合、ビューが実行されるまでセッションはフラッシュ/コミットされません。ビューでSQLエラーを自分でキャッチしたい場合は、flush
にSQLをエンジンに送信させる必要があります。 DBSession.flush()
は、add(...)
の後に実行する必要があります。
更新
トランザクションパッケージでこれを行う方法の例がほとんどないという理由だけで、セーブポイントの例でこの回答を更新しています。
def create_unique_object(db, max_attempts=3):
while True:
sp = transaction.savepoint()
try:
obj = MyObject()
obj.identifier = uuid.uuid4().hex
db.add(obj)
db.flush()
except IntegrityError:
sp.rollback()
max_attempts -= 1
if max_attempts < 1:
raise
else:
return obj
obj = create_unique_object(DBSession)
テーブルレベルのロックが使用されていない場合、これでもトランザクション間で重複する可能性がありますが、少なくともセーブポイントの使用方法を示していることに注意してください。
あなたがする必要があるのは、一般的な例外をキャッチし、そのクラスを出力することです。次に、例外をより具体的にすることができます。
except Exception as ex:
print ex.__class__
DBSession.commit()
までデータベース操作がない可能性があるため、try/except
を持つコントローラーコードが既に返された後、スタックの後半でIntegrityError
が発生します。
編集:上記の編集された回答は、ロールバックを使用してこれを行うためのより良い方法です。
-
ピラミッドアプリケーションの途中でトランザクションを処理する場合、またはシーケンスの最後に自動トランザクションコミットが実行される場合は、魔法をかける必要はありません。
前のトランザクションが失敗した場合は、新しいトランザクションを開始することを忘れないでください。
このような:
def my_view(request):
... # Do things
if success:
try:
instance = self._instance(**data)
DBSession.add(instance)
transaction.commit()
return {'success': True}
except IntegrityError as e: # <--- Oh no! Duplicate unique key
transaction.abort()
transaction.begin() # <--- Start new transaction
return {'success': False}
成功したトランザクションで.commit()を呼び出すことは問題ないため、成功した呼び出しの後に新しいトランザクションを開始する必要はないことに注意してください。
トランザクションが失敗状態の場合にのみ、トランザクションを中止して新しいトランザクションを開始する必要があります。
(トランザクションがそのような概念実証ではなかった場合は、新しいトランザクションを開始するのではなく、セーブポイントを使用してセーブポイントにロールバックできます。残念ながら、コミットを試みると、既知の以前のセーブポイントが無効になるため、それは不可能です。すばらしいですね。) (編集:<---私はそれについて間違っていることがわかりました...)
これが私のやり方です。
from contextlib import(
contextmanager,
)
@contextmanager
def session_scope():
"""Provide a transactional scope around a series of operations."""
session = Session()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
def create_user(email, firstname, lastname, password):
new_user = Users(email, firstname, lastname, password)
try:
with session_scope() as session:
session.add(new_user)
except sqlalchemy.exc.IntegrityError as e:
pass