web-dev-qa-db-ja.com

SQLAlchemyで整合性エラーをキャッチしようとしています

エラーをキャッチしようとして問題が発生しました。 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ステートメントは失敗していませんが成功している可能性があります。または、おそらく、このエラーを完全に間違ってキャッチしています。

16
Lostsoul

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)

テーブルレベルのロックが使用されていない場合、これでもトランザクション間で重複する可能性がありますが、少なくともセーブポイントの使用方法を示していることに注意してください。

21

あなたがする必要があるのは、一般的な例外をキャッチし、そのクラスを出力することです。次に、例外をより具体的にすることができます。

except Exception as ex:
    print ex.__class__
8
asthasr

DBSession.commit()までデータベース操作がない可能性があるため、try/exceptを持つコントローラーコードが既に返された後、スタックの後半でIntegrityErrorが発生します。

5
jfs

編集:上記の編集された回答は、ロールバックを使用してこれを行うためのより良い方法です。

-

ピラミッドアプリケーションの途中でトランザクションを処理する場合、またはシーケンスの最後に自動トランザクションコミットが実行される場合は、魔法をかける必要はありません。

前のトランザクションが失敗した場合は、新しいトランザクションを開始することを忘れないでください。

このような:

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()を呼び出すことは問題ないため、成功した呼び出しの後に新しいトランザクションを開始する必要はないことに注意してください。

トランザクションが失敗状態の場合にのみ、トランザクションを中止して新しいトランザクションを開始する必要があります。

(トランザクションがそのような概念実証ではなかった場合は、新しいトランザクションを開始するのではなく、セーブポイントを使用してセーブポイントにロールバックできます。残念ながら、コミットを試みると、既知の以前のセーブポイントが無効になるため、それは不可能です。すばらしいですね。) (編集:<---私はそれについて間違っていることがわかりました...)

1
Doug

これが私のやり方です。

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

http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when- do-i-close-it

1
The Demz