Error Messages

この項では、SQLAlchemyによって生成または出力される一般的なエラーメッセージと警告の説明とバックグラウンドを示します。

SQLAlchemyは通常、SQLAlchemy固有の例外クラスのコンテキスト内でエラーを発生させます。これらのクラスの詳細については、 Core Exceptions および ORM Exceptions を参照してください。

SQLAlchemyエラーは、 プログラミング時エラー実行時エラー の2つのカテゴリに大別できます。プログラミング時エラーは、関数またはメソッドが不正な引数で呼び出された結果、または解決できないマッパー構成などの他の構成指向のメソッドから呼び出された結果として発生します。プログラミング時エラーは、通常、即時かつ決定的なものです。一方、実行時エラーは、データベース接続の枯渇やデータ関連の問題の発生など、任意に発生する何らかの条件に応答してプログラムが実行されるときに発生する障害を表します。実行時エラーは、プログラムがロードやデータの発生に応答してこれらの状態に遭遇するため、実行中のアプリケーションのログに表示される可能性が高くなります。

実行時エラーは再現が容易ではなく、プログラムの実行中に何らかの任意の条件に応じて発生することが多いため、デバッグがより困難になり、また、すでにプロダクション環境に置かれているプログラムにも影響を与えます。このセクションでは、最も一般的な実行時エラーとプログラミング時エラーのバックグラウンドを説明します。

Connections and Transactions

QueuePool limit of size <x> overflow <y> reached, connection timed out, timeout <z>

これは、アプリケーションの作業負荷が設定された制限を超えることに直接関係するため、おそらく最も一般的なランタイムエラーであり、通常、ほとんどすべてのSQLAlchemyアプリケーションに適用されます。

以下に、このエラーが何を意味するかをまとめます。まず、ほとんどのSQLAlchemyユーザが既に知っているはずの最も基本的な点から説明します。

  • SQLAlchemy Engineオブジェクトはデフォルトで接続プールを使用します - これが意味するのは、 Engine オブジェクトのSQLデータベース接続リソースを使用し、そのリソースを releases した場合、データベース接続自体はデータベースに接続されたまま内部キューに戻され、そこで再び使用できるということです。コードがデータベースとの対話を終了しているように見えても、多くの場合、アプリケーションはアプリケーションが終了するか、プールが明示的に破棄されるまで、固定された数のデータベース接続を維持します。

  • プールのため、アプリケーションがSQLデータベース接続を使用する場合、最も一般的には Engine.connect() を使用する場合、またはORM Session を使用してクエリを行う場合、このアクティビティは、接続オブジェクトが取得された時点でデータベースへの新しい接続を確立するとは限りません。代わりに、接続プールに接続を問い合わせ、多くの場合、既存の接続をプールから取得して再利用します。使用可能な接続がない場合、プールは新しいデータベース接続を作成しますが、プールが設定された容量を超えていない場合に限られます。

  • ほとんどの場合、デフォルトのプールは QueuePool と呼ばれます。このプールに接続を要求しても使用可能な接続がない場合、新しい接続が作成されます。 実行中の接続の合計数が設定された値よりも少ない場合 新しい接続が作成されます。この値は、 プールサイズに最大オーバーフローを加えた値 に等しくなります。つまり、エンジンを次のように設定した場合:

    engine = create_engine("mysql+mysqldb://u:p@host/db", pool_size=10, max_overflow=20)

より多くの接続を一度に使用できるようにするために、 create_engine() 関数に渡される create_engine.pool_size および: paramref:_sa.create_engine.max_overflow パラメータを使用してプールを調整できます。接続が使用可能になるのを待つタイムアウトは、 create_engine.pool_timeout パラメータを使用して設定されます。

  • create_engine.max_overflow を値”-1”に設定することで、無制限のオーバーフローを持つようにプールを設定できます。この設定では、プールは接続の固定プールを維持しますが、新しい接続が要求されたときにブロックされることはありません。代わりに、使用可能な接続がない場合は無条件に新しい接続を作成します。

アプリケーションが利用可能なすべての接続を使い切る原因は何ですか?

  • プールに構成された値に基づいて作業するには、アプリケーションが処理している同時リクエストが多すぎます - これが最も直接的な原因です。30の同時スレッドを許可するスレッドプールで実行されるアプリケーションがあり、スレッドごとに1つの接続が使用されている場合、プールが一度に少なくとも30の接続をチェックアウトできるように構成されていないと、アプリケーションが十分な同時リクエストを受け取ると、このエラーが発生します。解決策は、プールの制限を上げるか、同時スレッドの数を減らすことです。

  • アプリケーションがプールへの接続を返していないません - これは次に多い理由で、アプリケーションは接続プールを利用していますが、プログラムがこれらの接続を release できず、代わりに開いたままにしています。接続プールとORM Session には、セッションや接続オブジェクトがガベージコレクションされると、基礎となる接続リソースが解放されるようなロジックがありますが、この動作を信頼してリソースをタイムリーに解放することはできません。

  • アプリケーションは長時間実行トランザクションを実行しようとしています - データベーストランザクションは非常に高価なリソースであり、 何らかのイベントが発生するのを待ってアイドル状態にしておくべきではありません 。アプリケーションが、ユーザーがボタンを押すのを待っている場合、長時間実行ジョブキューから結果が返されるのを待っている場合、またはブラウザへの永続的な接続を開いたままにしている場合は、データベーストランザクションを開いたままにしておく必要はありません。アプリケーションはデータベースを操作し、イベントと対話する必要があるため、その時点で短時間実行トランザクションを開いてから閉じます。

  • アプリケーションがデッドロックしています-これもこのエラーの一般的な原因ですが、アプリケーション側またはデータベース側のデッドロックのためにアプリケーションが接続の使用を完了できない場合、アプリケーションは使用可能なすべての接続を使い果たし、追加の要求がこのエラーを受け取ることになります。デッドロックの理由は次のとおりです。

プーリングを使用する代わりに、プーリングを完全にオフにする方法もあることに注意してください。このバックグラウンドについては Switching Pool Implementations の節を参照してください。ただし、このエラーメッセージが発生している場合は、 常に アプリケーション自体のより大きな問題が原因であることに注意してください。プールは問題をより早く明らかにするのに役立ちます。

Pool class cannot be used with asyncio engine (or vice versa)

QueuePool プールクラスは内部的には thread.Lock オブジェクトを使用しており、asyncioとは互換性がありません。 create_async_engine() 関数を使用して AsyncEngine を作成する場合、適切なキュープールクラスは AsyncAdaptedQueuePool です。これは自動的に使用され、指定する必要はありません。

AsyncAdaptedQueuePool に加えて、 NullPoolStaticPool プールクラスはロックを使用せず、非同期エンジンでの使用にも適しています。

AsyncAdaptedQueuePool プールクラスが create_engine() 関数で明示的に指定されている場合にも、このエラーは逆に発生します。

Can’t reconnect until invalid transaction is rolled back. Please rollback() fully before proceeding

このエラー条件は、データベースの切断が検出されたか、 Connection.invalidate() が明示的に呼び出されたために Connection が無効になったが、 Connection.begin() メソッドによって明示的に開始されたトランザクションがまだ存在する場合、またはSQL文が発行されたときにSQLAlchemyの2.xシリーズで発生するように接続が自動的にトランザクションを開始した場合に発生します。接続が無効になると、進行中だった Transaction は無効な状態になり、 Connection から削除するために明示的にロールバックする必要があります。

DBAPI Errors

PythonデータベースAPI(DBAPI)は、 Pep-249 にあるデータベースドライバの仕様です。このAPIは、データベースのあらゆる障害モードに対応する一連の例外クラスを指定します。

SQLAlchemyはこれらの例外を直接生成しません。代わりに、それらはデータベースドライバからインターセプトされ、SQLAlchemyが提供する例外 DBAPIError によってラップされますが、例外内のメッセージは SQLAlchemyではなく、ドライバによって生成されます

InterfaceError

データベース自体ではなく、データベースインターフェースに関連するエラーに対して発生する例外です。

このエラーは DBAPI Error であり、SQLAlchemy自身ではなく、データベースドライバ(DBAPI)から発生します。

InterfaceError は、データベース接続が切断されたり、データベースに接続できない状況で、ドライバによって発生することがあります。この問題に対処するためのヒントについては、 Dealing with Disconnects を参照してください。

DatabaseError

渡されるインタフェースまたはデータではなく、データベース自体に関連するエラーに対して発生する例外です。

このエラーは DBAPI Error であり、SQLAlchemy自身ではなく、データベースドライバ(DBAPI)から発生します。

DataError

ゼロによる除算、範囲外の数値など、処理されたデータの問題に起因するエラーに対して発生する例外です。

このエラーは DBAPI Error であり、SQLAlchemy自身ではなく、データベースドライバ(DBAPI)から発生します。

OperationalError

データベースの操作に関連し、必ずしもプログラマの制御下にないエラーに対して発生する例外です。たとえば、予期しない切断が発生した、データソース名が見つからなかった、トランザクションを処理できなかった、処理中にメモリ割り当てエラーが発生したなど。

このエラーは DBAPI Error であり、SQLAlchemy自身ではなく、データベースドライバ(DBAPI)から発生します。

OperationalError は最も一般的な(しかし唯一ではない)エラークラスで、データベース接続が切断されたり、データベースに接続できなくなったりした場合にドライバが使用します。これに対処するためのヒントについては Dealing with Disconnects を参照してください。

IntegrityError

外部キーチェックが失敗するなど、データベースのリレーショナル整合性が影響を受ける場合に発生する例外です。

このエラーは DBAPI Error であり、SQLAlchemy自身ではなく、データベースドライバ(DBAPI)から発生します。

InternalError

データベースで内部エラーが発生した場合に発生する例外です。たとえば、カーソルが無効になった、トランザクションが同期していないなどです。

このエラーは DBAPI Error であり、SQLAlchemy自身ではなく、データベースドライバ(DBAPI)から発生します。

InternalError は、データベース接続が切断されたり、データベースに接続できなかったりする状況で、ドライバによって発生することがあります。この問題に対処するためのヒントについては、 Dealing with Disconnects を参照してください。

ProgrammingError

プログラミングエラーの場合に発生する例外です。たとえば、テーブルが見つからない、またはすでに存在する、SQL文の構文エラー、指定されたパラメータの数が間違っているなど。

このエラーは DBAPI Error であり、SQLAlchemy自身ではなく、データベースドライバ(DBAPI)から発生します。

データベース接続が切断された場合や、データベースに接続できない場合に、ドライバによって ProgrammingError が発生することがあります。これに対処するためのヒントについては、 Dealing with Disconnects を参照してください。

NotSupportedError

データベースでサポートされていないメソッドまたはデータベースAPIが使用された場合に発生する例外です。たとえば、トランザクションをサポートしていない接続またはトランザクションがオフになっている接続で .rollback()を要求した場合などです。

このエラーは DBAPI Error であり、SQLAlchemy自身ではなく、データベースドライバ(DBAPI)から発生します。

SQL Expression Language

Object will not produce a cache key, Performance Implications

バージョン1.4のSQLAlchemyには SQL compilation caching facility が含まれています。これにより、CoreとORM SQL構文が、結果を文から取得するために使用される他の構造情報と共に、文字列化された形式をキャッシュできるようになり、構造的に等価な別の構文が次に使用されるときに、比較的高価な文字列コンパイルプロセスをスキップできるようになります。このシステムは、 Columnselect()TypeEngine オブジェクトなどのオブジェクトを含むすべてのSQL構文に実装されている機能に依存して、SQLコンパイルプロセスに影響を与える程度までそれらの状態を完全に表現する キャッシュキー を生成します。

問題の警告が Column オブジェクトのような広く使用されているオブジェクトを参照していて、( Estimating Cache Performance Using Logging で説明されている推定テクニックを使って)発行されるSQL構文の大部分に影響を与えていることが示され、一般的にアプリケーションでキャッシングが有効になっていない場合、これはパフォーマンスに悪影響を与え、場合によっては以前のバージョンのSQLAlchemyと比較して パフォーマンスの低下 を引き起こす可能性があります。 Why is my application slow after upgrading to 1.4 and/or 2.x? のFAQでは、これについてさらに詳しく説明しています。

Caching disables itself if there’s any doubt

キャッシュは、文の 完全な構造一貫性のある 方法で正確に表すキャッシュキーを生成できるかどうかに依存します。特定のSQL構文(または型)に、適切なキャッシュキーの生成を可能にする適切なディレクティブがない場合、キャッシュを安全に有効化できません。

  • キャッシュキーは 完全な構造 を表している必要があります。その構文の2つの別々のインスタンスを使用すると異なるSQLがレンダリングされる可能性がある場合、最初の要素と2番目の要素の明確な違いを取得しないキャッシュキーを使用して、要素の最初のインスタンスに対してSQLをキャッシュすると、2番目のインスタンスに対して不正なSQLがキャッシュされてレンダリングされます。

  • キャッシュキーは 一貫性がある 必要があります。構文が毎回変化する状態(リテラル値など)を表し、そのすべてのインスタンスに対して一意のSQLを生成する場合、この構文をキャッシュしても安全ではありません。これは、構文を繰り返し使用すると、文キャッシュが一意のSQL文字列ですぐにいっぱいになり、二度と使用されない可能性が高いため、キャッシュの目的が損なわれるためです。

上記の2つの理由から、SQLAlchemyのキャッシュシステムは、オブジェクトに対応するSQLをキャッシュすることを決定することに関して 非常に保守的 です。

Assertion attributes for caching

この警告は以下の基準に基づいて発せられます。それぞれの詳細については Why is my application slow after upgrading to 1.4 and/or 2.x? を参照してください。

  • Dialect 自体(つまり、 create_engine() に渡すURLの最初の部分で指定されるモジュール。例えば、 postgresql+psycopg2:// )は、キャッシュを正しくサポートするためにレビューされ、テストされたことを示す必要があります。これは、 Dialect.supports_statement_cache 属性が True に設定されていることで示されます。サードパーティのダイアレクトを使用する場合は、ダイアレクトのメンテナに相談して、彼らのダイアレクトで steps to ensure caching may be enabled に従って新しいリリースを公開してください。

  • TypeDecorator または UserDefinedType のいずれかを継承するサードパーティまたはユーザ定義の型は、 ExternalType.cache_ok 属性をその定義に含める必要があります。これには 、ExternalType.cache_ok のdocstringで説明されているガイドラインに従って、すべての派生サブクラスも含まれます。以前と同様に、これらのデータ型がサードパーティのライブラリからインポートされている場合は、そのライブラリのメンテナと相談して、ライブラリに必要な変更を加え、新しいリリースを公開してください。

See also

Estimating Cache Performance Using Logging - キャッシュの動作と効率を観測したバックグラウンド

Why is my application slow after upgrading to 1.4 and/or 2.x? - Frequently Asked Questions セクション

Compiler StrSQLCompiler can’t render element of type <element type>

このエラーは通常、デフォルトのコンパイルの一部ではない要素を含むSQL式の構成体を文字列化しようとしたときに発生します。この場合、エラーは StrSQLCompiler クラスに対して発生します。あまり一般的ではありませんが、特定の種類のデータベースバックエンドで間違った種類のSQL式が使用された場合にも発生することがあります。そのような場合には、 SQLCompilersqlalchemy.dialects.postgresql.PGCompiler など、他の種類のSQLコンパイラクラスに名前が付けられます。以下のガイダンスは、”文字列化”のユースケースに特化していますが、一般的なバックグラウンドについても説明しています。

通常、コアSQL構文またはORM Query オブジェクトは、 print() のように直接文字列化することができます。

>>> from sqlalchemy import column
>>> print(column("x") == 5)
x = :x_1

上記のSQL式が文字列化されると、 StrSQLCompiler コンパイラクラスが使用されます。これは、構文が方言固有の情報なしに文字列化されたときに呼び出される特別なステートメントコンパイラです。

しかし、ある特定の種類のデータベース言語に固有の多くの構文があります。例えば、PostgreSQLの “insert on conflict” 構文のように、 StrSQLCompiler は文字列に変換する方法を知りません:

>>> from sqlalchemy.dialects.postgresql import insert
>>> from sqlalchemy import table, column
>>> my_table = table("my_table", column("x"), column("y"))
>>> insert_stmt = insert(my_table).values(x="foo")
>>> insert_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["y"])
>>> print(insert_stmt)
Traceback (most recent call last):

...

sqlalchemy.exc.UnsupportedCompilationError: Compiler <sqlalchemy.sql.compiler.StrSQLCompiler object at 0x7f04fc17e320> can't render element of type <class 'sqlalchemy.dialects.postgresql.dml.OnConflictDoNothing'>

特定のバックエンドに固有の構文を文字列化するには、 ClauseElement.compile() メソッドを使用して、正しいコンパイラを呼び出す Engine または Dialect オブジェクトを渡す必要があります。以下ではPostgreSQLのダイアレクトを使用します:

>>> from sqlalchemy.dialects import postgresql
>>> print(insert_stmt.compile(dialect=postgresql.dialect()))
INSERT INTO my_table (x) VALUES (%(x)s) ON CONFLICT (y) DO NOTHING

ORM Query オブジェクトの場合、この文は Query.statement アクセッサを使ってアクセスできます:

statement = query.statement
print(statement.compile(dialect=postgresql.dialect()))

SQL要素の直接のストリング化/コンパイルの詳細については、以下のFAQリンクを参照してください。

TypeError: <operator> not supported between instances of ‘ColumnProperty’ and <something>

これは、 column_property() または deferred() オブジェクトをSQL式のコンテキストで、通常は次のような宣言内で使用しようとしたときによく起こります:

class Bar(Base):
    __tablename__ = "bar"

    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))

    __table_args__ = (CheckConstraint(cprop > 5),)

上記では、 cprop 属性はマップされる前にインラインで使用されていますが、この cprop 属性は Column ではなく、 ColumnProperty です。これは一時的なオブジェクトなので、宣言プロセスが完了すると Bar クラスにマップされる Column オブジェクトや InstrumentedAttribute オブジェクトの完全な機能を持ちません。

ColumnProperty には、いくつかの列指向のコンテキストで動作することを可能にする __clause_element__() メソッドがありますが、上で説明したようなオープンエンドの比較コンテキストでは動作しません。なぜなら、数値”5”との比較を通常のPythonの比較ではなくSQL式として解釈できるPythonの __eq__() メソッドがないからです。

解決策は、 ColumnProperty.expression 属性を使って直接 Column にアクセスすることです:

class Bar(Base):
    __tablename__ = "bar"

    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))

    __table_args__ = (CheckConstraint(cprop.expression > 5),)

A value is required for bind parameter <x> (in parameter group <y>)

このエラーは、ステートメントが暗黙的または明示的に bindparam() を使用し、ステートメントの実行時に値を提供しない場合に発生します:

stmt = select(table.c.column).where(table.c.id == bindparam("my_param"))

result = conn.execute(stmt)

上記では、パラメータ”my_param”に値が指定されていません。正しい方法は、値を指定することです:

result = conn.execute(stmt, {"my_param": 12})

メッセージが “a value is required for bind parameter <x> in parameter group <y>”の形式をとる場合、メッセージは”executemany”スタイルの実行を参照しています。この場合、ステートメントは通常INSERT、UPDATE、またはDELETEで、パラメータのリストが渡されます。この形式では、ステートメントは引数リストで指定されたすべてのパラメータのパラメータ位置を含むように動的に生成され、 最初のパラメータセット を使用して、これらが何であるべきかを決定します。

たとえば、次の文は、パラメータ”a”、”b”、および”c”を必要とする最初のパラメータセットに基づいて計算されます。これらの名前は、リスト内の各パラメータセットに使用される文の最終的な文字列形式を決定します。2番目のエントリに”b”が含まれていないため、このエラーが生成されます:

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))

e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError) A value is required for bind parameter 'b', in parameter group 1 [SQL: u'INSERT INTO t (a, b, c) VALUES (?, ?, ?)'] [parameters: [{'a': 1, 'c': 3, 'b': 2}, {'a': 2, 'c': 4}, {'a': 3, 'c': 5, 'b': 4}]]

“b”は必須なので、INSERTが実行されるように、”None”として渡します:

e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "b": None, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)

Expected FROM clause, got Select. To create a FROM clause, use the .subquery() method

これはSQLAlchemy 1.4で行われた変更で、 select() のような関数によって生成され、共用体やテキストのSELECT式のようなものを含むSELECT文は、 FromClause オブジェクトとは見なされなくなり、最初に Subquery でラップされない限り、別のSELECT文のFROM句に直接配置することはできなくなりました。これはコアの主要な概念上の変更であり、完全な理論的根拠は A SELECT statement is no longer implicitly considered to be a FROM clause で議論されています。

Given an example as:

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
stmt = select(t)

上の例では、 stmt はSELECT文を表しています。このエラーは、別のSELECTのFROM句として stmt を直接使用したい場合に発生します。たとえば、そこから選択しようとした場合などです:

new_stmt_1 = select(stmt)

または、JOINなどのFROM句で使用する場合:

new_stmt_2 = select(some_table).select_from(some_table.join(stmt))

以前のバージョンのSQLAlchemyでは、別のSELECTの内部でSELECTを使用すると、括弧で括られた名前のない副問い合わせが生成されました。MySQLやPostgreSQLのようなデータベースでは、FROM句内の副問い合わせが名前付きのエイリアスを持つ必要があるため、ほとんどの場合、この形式のSQLはあまり有用ではありません。つまり、 SelectBase.alias() メソッドを使用するか、1.4では SelectBase.subquery() メソッドを使用してこれを生成します。他のデータベースでは、副問い合わせ内の列名への将来の参照に関するあいまいさを解決するために、副問い合わせが名前を持つ方がはるかに明確です。

上記の実際的な理由以外にも、変更が行われているSQLAlchemy指向の理由はたくさんあります。したがって、上記の2つのステートメントの正しい形式では、 SelectBase.subquery() を使用する必要があります:

subq = stmt.subquery()

new_stmt_1 = select(subq)

new_stmt_2 = select(some_table).select_from(some_table.join(subq))

An alias is being generated automatically for raw clauseelement

New in version 1.4.26.

この非推奨警告は、レガシーの Query.join() メソッドと 2.0 style Select.join() メソッドに適用される非常に古く、あまり知られていないパターンを参照しています。ここで、結合は relationship() で記述できますが、ターゲットは、マップされたクラスや aliased() 構成体のようなORMエンティティではなく、クラスがマップされる Table やその他のCore選択可能なものです:

a1 = Address.__table__

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
    .all()
)

上記のパターンでは、Core JoinAlias オブジェクトのような任意の選択が可能ですが、この要素は自動的には適用されないので、Core要素を直接参照する必要があります:

a1 = Address.__table__.alias()

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(a1.c.email_address == "ed@foo.com")
    .all()
)

結合対象を指定する正しい方法は、常にマップされたクラスそのものか aliased オブジェクトを使うことです。後者の場合は PropComparator.of_type() 修飾子を使ってエイリアスを設定します:

# normal join to relationship entity
q = s.query(User).join(User.addresses).filter(Address.email_address == "ed@foo.com")

# name Address target explicitly, not necessary but legal
q = (
    s.query(User)
    .join(Address, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
)

エイリアスへの結合:

from sqlalchemy.orm import aliased

a1 = aliased(Address)

# of_type() form; recommended
q = (
    s.query(User)
    .join(User.addresses.of_type(a1))
    .filter(a1.email_address == "ed@foo.com")
)

# target, onclause form
q = s.query(User).join(a1, User.addresses).filter(a1.email_address == "ed@foo.com")

An alias is being generated automatically due to overlapping tables

New in version 1.4.26.

この警告は通常、 Select.join() メソッドまたは従来の Query.join() メソッドを使用して、結合テーブルの継承を含むマッピングでクエリを行うときに生成されます。問題は、共通のベーステーブルを共有する2つの結合された継承モデル間を結合する場合、2つのエンティティ間の適切なSQL JOINは、どちらかの側にエイリアスを適用しないと形成できないことです。SQLAlchemyは、結合の右側にエイリアスを適用します。たとえば、結合された継承マッピングが次のように与えられたとします:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    manager_id = Column(ForeignKey("manager.id"))
    name = Column(String(50))
    type = Column(String(50))

    reports_to = relationship("Manager", foreign_keys=manager_id)

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }


class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "inherit_condition": id == Employee.id,
    }

上記のマッピングには、 Employee クラスと Manager クラスの間の関係が含まれています。どちらのクラスも employee データベーステーブルを使用しているので、SQLの観点から見ると、これは self_referential relationship です。結合を使用して Employee モデルと Manager モデルの両方からクエリを実行したい場合、SQLレベルでは``employee`` テーブルをクエリに2回含める必要があります。つまり、エイリアスを作成する必要があります。SQLAlchemy ORMを使用してこのような結合を作成すると、次のようなSQLが得られます。

>>> stmt = select(Employee, Manager).join(Employee.reports_to)
>>> print(stmt)
SELECT employee.id, employee.manager_id, employee.name, employee.type, manager_1.id AS id_1, employee_1.id AS id_2, employee_1.manager_id AS manager_id_1, employee_1.name AS name_1, employee_1.type AS type_1 FROM employee JOIN (employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id) ON manager_1.id = employee.manager_id

上の例では、SQLはクエリ内の Employee エンティティを表す employee テーブルから選択します。次に、 employee AS employee_1 JOIN manager AS manager_1 の右入れ子結合に結合します。ここでは、匿名のエイリアス employee_1 を除いて、 employee テーブルが再度記述されています。これは、警告メッセージが参照する”エイリアスの自動生成”です。

SQLAlchemyがそれぞれ Employee オブジェクトと Manager オブジェクトを含むORM行をロードする時、ORMは上にある employee_1manager_1 というテーブルエイリアスからの行を、エイリアスされていない Manager クラスの行に適合させなければなりません。このプロセスは内部的に複雑で、すべてのAPI機能に対応しているわけではありません。特に、 contains_eager() のようなEagerローディング機能を、ここに示すよりも深くネストされたクエリで使用しようとする場合にはそうです。このパターンは、より複雑なシナリオでは信頼できず、予測や追跡が困難な暗黙の意思決定を伴うため、警告が表示され、このパターンはレガシー機能と見なされる可能性があります。この問い合わせを書くためのより良い方法は、他の自己参照関係に適用されるものと同じパターンを使うことです。つまり、 aliased() 構文を明示的に使うことです。結合継承やその他の結合指向のマッピングでは、通常、 aliased.flat パラメータの使用を追加することが望まれます。これにより、結合を新しい副問い合わせに埋め込むのではなく、結合内の個々のテーブルにエイリアスを適用することで、2つ以上のテーブルのJOINにエイリアスを適用できます:

>>> from sqlalchemy.orm import aliased
>>> manager_alias = aliased(Manager, flat=True)
>>> stmt = select(Employee, manager_alias).join(Employee.reports_to.of_type(manager_alias))
>>> print(stmt)
SELECT employee.id, employee.manager_id, employee.name, employee.type, manager_1.id AS id_1, employee_1.id AS id_2, employee_1.manager_id AS manager_id_1, employee_1.name AS name_1, employee_1.type AS type_1 FROM employee JOIN (employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id) ON manager_1.id = employee.manager_id

contains_eager() を使って reports_to 属性を生成したい場合は、エイリアスを参照します:

>>> stmt = (
...     select(Employee)
...     .join(Employee.reports_to.of_type(manager_alias))
...     .options(contains_eager(Employee.reports_to.of_type(manager_alias)))
... )

明示的な aliased() オブジェクトを使用しないと、入れ子になったいくつかのケースでは、 contains_eager() オプションは、データをどこから取得するかを知るための十分なコンテキストを持っていません。これは、ORMが非常に入れ子になったコンテキストで”auto-aliasing”している場合です。したがって、この機能に頼らず、代わりにSQL構文をできるだけ明示的にしておくことが最善です。

Object Relational Mapping

IllegalStateChangeError and concurrency exceptions

SQLAlchemy 2.0は、 Session raises proactively when illegal concurrent or reentrant access is detected で説明されている新しいシステムを導入しました。これは、 Session オブジェクトの個々のインスタンス、さらには AsyncSession プロキシオブジェクトで呼び出されている並行メソッドを事前に検出します。これらの並行アクセス呼び出しは、排他的ではありませんが、通常、 Session の単一インスタンスが、そのようなアクセスが同期されていない複数の並行スレッド間で共有されている場合、または同様に、 AsyncSession の単一インスタンスが複数の並行タスク間で共有されている場合(例えば、 asyncio.gather() のような関数を使用している場合)に発生します。これらの使用パターンは、これらのオブジェクトの適切な使用ではありません。事前警告システムがなければ、SQLAlchemy実装はオブジェクト内に無効な状態を生成し、データベース接続自体にドライバレベルのエラーを含むデバッグ困難なエラーを生成します。

SessionAsyncSession のインスタンスは、メソッド呼び出しの同期 が組み込まれていない 可変のステートフルなオブジェクトであり、オブジェクトがバインドされている特定の Engine または AsyncEngine に対する一度に1つのデータベース接続で、 単一の進行中のデータベーストランザクション を表します(これらのオブジェクトは両方とも同時に複数のエンジンにバインドすることをサポートしていますが、この場合、トランザクションのスコープ内で動作するエンジンごとに1つの接続しかありません)。単一のデータベーストランザクションは、並行SQLコマンドの適切なターゲットではありません。代わりに、並行データベース操作を実行するアプリケーションは並行トランザクションを使用する必要があります。これらのオブジェクトの場合、適切なパターンはスレッドごとの Session か、タスクごとの AsyncSession になります。

並行性のバックグラウンドについては Is the Session thread-safe? Is AsyncSession safe to share in concurrent tasks? の節を参照してください。

Parent instance <x> is not bound to a Session; (lazy load/deferred load/refresh/etc.) operation cannot proceed

これはORMを扱うときに最も一般的なエラーメッセージであり、ORMが広く使用している lazy loading として知られるテクニックの性質の結果として発生します。遅延読み込みは、ORMによって永続化されたオブジェクトがデータベース自体へのプロキシを維持する一般的なオブジェクトリレーショナルパターンであり、オブジェクト上のさまざまな属性がアクセスされると、それらの値がデータベースから 遅延 して取得されます。このアプローチの利点は、すべての属性または関連データを一度にロードすることなくオブジェクトをデータベースから取得でき、代わりに要求されたデータのみをその時点で配信できることです。主な欠点は、基本的に利点の反対であり、すべての場合に特定のデータセットを必要とすることが知られている多くのオブジェクトがロードされている場合、その追加データを断片的にロードするのは無駄であるということです。

通常の効率性の問題を超えた遅延読み込みのもう1つの注意事項は、遅延読み込みが進行するためには、オブジェクトがその状態を取得できるように、 セッションに関連付けられたまま でなければならないことです。このエラーメッセージは、オブジェクトがその Session との関連付けが解除され、データベースからデータを遅延読み込みするように要求されていることを示します。

オブジェクトが Session から切り離される最も一般的な理由は、セッション自体が、通常は Session.close() メソッドによって閉じられたことです。その後、オブジェクトはさらにアクセスされるために存続します。多くの場合、オブジェクトはWebアプリケーション内でサーバ側のテンプレートエンジンに配信され、ロードできない追加の属性が要求されます。

このエラーを軽減するには、次のテクニックを使用します。

  • デタッチされたオブジェクトを持たないようにしてください。セッションを早めに閉じないでください - 多くの場合、アプリケーションは、このエラーのために失敗する他のシステムに関連するオブジェクトを渡す前に、トランザクションを閉じます。トランザクションをすぐに閉じる必要がない場合もあります。たとえば、Webアプリケーションは、ビューがレンダリングされる前にトランザクションを閉じます。これはしばしば「正当性」という名前で行われますが、この用語は実際のアクションではなくコード編成を指すため、「カプセル化」の誤った適用と見なされることがあります。ORMオブジェクトを使用するテンプレートは、呼び出し元からデータベースロジックをカプセル化したままにする proxy pattern を使用しています。オブジェクトのライフスパンが完了するまで Session を開いたままにできる場合、これが最善のアプローチです。

  • そうでない場合は、必要なものをすべて事前にロードしてください - トランザクションを開いたままにしておくことは、特に、同じプロセス内にあっても同じコンテキストで実行できない他のシステムにオブジェクトを渡す必要がある、より複雑なアプリケーションでは、非常に多くの場合不可能です。この場合、アプリケーションは detached オブジェクトを処理する準備をし、 eager loading を適切に使用して、オブジェクトが事前に必要なものを持っていることを確認する必要があります。

  • そして重要なのは、expire_on_commitをFalseに設定することです - デタッチされたオブジェクトを使用する場合、オブジェクトがデータを再ロードする必要がある最も一般的な理由は、オブジェクトが Session.commit() の最後の呼び出しから期限切れになっているためです。この期限切れはデタッチされたオブジェクトを扱うときには使用すべきではありません。そのため、 Session.expire_on_commit パラメータを False に設定します。オブジェクトがトランザクションの外部で期限切れにならないようにすることで、ロードされたデータはそのまま残り、そのデータにアクセスしたときに追加の遅延ロードが発生することはありません。

This Session’s transaction has been rolled back due to a previous exception during flush

Flushing で説明されている Session のフラッシュプロセスは、エラーが発生した場合、内部の一貫性を維持するためにデータベーストランザクションをロールバックします。しかし、これが発生すると、セッションのトランザクションは「非アクティブ」になり、失敗が発生しなかった場合に明示的にコミットする必要があるのと同じ方法で、呼び出し側アプリケーションによって明示的にロールバックする必要があります。

これはORMを使うときによくあるエラーで、通常は Session 操作の周りに正しい”枠組み”がないアプリケーションに当てはまります。詳細は “This Session’s transaction has been rolled back due to a previous exception during flush.” (or similar) のFAQで説明されています。

For relationship <relationship>, delete-orphan cascade is normally configured only on the “one” side of a one-to-many relationship, and not on the “many” side of a many-to-one or many-to-many relationship.

このエラーは、”delete-orphan” cascade が以下のような多対1または多対多の関係に設定されている場合に発生します:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    # this will emit the error message when the mapper
    # configuration step occurs
    a = relationship("A", back_populates="bs", cascade="all, delete-orphan")


configure_mappers()

上記の B.a の”delete-orphan”設定は、特定の A を参照するすべての B オブジェクトが削除された場合、 A も削除されるべきであるという意図を示しています。つまり、削除される”orphan”は A オブジェクトであり、それを参照するすべての B が削除されると”orphan”になることを表しています。

“delete-orphan”カスケードモデルでは、この機能はサポートされていません。”orphan”の考慮は、単一のオブジェクトの削除に関してのみ行われます。このオブジェクトは、この単一の削除によって”orphand”にされた0個以上のオブジェクトを参照します。その結果、これらのオブジェクトも削除されます。つまり、孤立オブジェクトごとに1つのみの”親”オブジェクトを削除することに基づいて、”orphan”の作成を追跡するように設計されています。これは、1対多の関係では自然なケースであり、”1”側のオブジェクトを削除すると、”多”側の関連アイテムも削除されます。

この機能をサポートするための上記のマッピングでは、代わりにカスケード設定を1対多の側に置きます。これは次のようになります:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a", cascade="all, delete-orphan")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship("A", back_populates="bs")

A が削除されると、それが参照する B オブジェクトのすべても削除されるという意図が表明されている場合です。

次に、エラーメッセージは relationship.single_parent フラグの使用法を示唆します。このフラグは、特定のオブジェクトを参照する多くのオブジェクトを持つことができる関係が、実際には一度に 1つの オブジェクトしか参照しないことを強制するために使用できます。これは、外部キー関係が”多くの”コレクションを示唆するレガシーまたはその他のあまり理想的でないデータベーススキーマに使用されますが、実際には一度に1つのオブジェクトのみが特定のターゲットオブジェクトを参照します。このまれなシナリオは、次のように上記の例で示すことができます:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship(
        "A",
        back_populates="bs",
        single_parent=True,
        cascade="all, delete-orphan",
    )

上記の設定は、 B.a 関係の範囲内で、一度に1つの B だけが A に関連付けられることを強制するバリデータをインストールします:

>>> b1 = B()
>>> b2 = B()
>>> a1 = A()
>>> b1.a = a1
>>> b2.a = a1
sqlalchemy.exc.InvalidRequestError: Instance <A at 0x7eff44359350> is
already associated with an instance of <class '__main__.B'> via its
B.a attribute, and is only allowed a single parent.

このバリデータの有効範囲は限られており、複数の”親”が別の方向から作成されるのを防ぐことはできないことに注意してください。たとえば、 A.bs に関しては同じ設定は検出されません。

>>> a1.bs = [b1, b2]
>>> session.add_all([a1, b1, b2])
>>> session.commit()
INSERT INTO a DEFAULT VALUES () INSERT INTO b (a_id) VALUES (?) (1,) INSERT INTO b (a_id) VALUES (?) (1,)

しかし、”delete-orphan”カスケードは 単一の リードオブジェクトに関して機能し続けるので、後で物事は期待通りに進みません。つまり、 B オブジェクトの いずれか を削除すると、 A が削除されます。もう1つの B は近くにあり、ORMは通常、外部キー属性をNULLに設定するのに十分な賢さを持っていますが、通常はこれは望まれていることではありません。

>>> session.delete(b1)
>>> session.commit()
UPDATE b SET a_id=? WHERE b.id = ? (None, 2) DELETE FROM b WHERE b.id = ? (1,) DELETE FROM a WHERE a.id = ? (1,) COMMIT

上記のすべての例で、同様のロジックが多対多関係の計算に適用されます。多対多関係の一方でsingle_parent=Trueが設定されている場合、その側で”delete-orphan”カスケードを使用できます。ただし、多対多関係のポイントは、どちらの方向にもオブジェクトを参照する多くのオブジェクトが存在できるようにすることであるため、これが実際に必要とされるものである可能性は非常に低くなります。

全体として、 “delete-orphan”カスケードは通常、1対多関係の”1”側に適用されるため、”多”側のオブジェクトは削除されますが、その逆は行われません。

Changed in version 1.3.18: 多対1または多対多の関係で使用された場合の”delete-orphan”エラーメッセージのテキストが、より説明的に更新されました。

Instance <instance> is already associated with an instance of <instance> via its <attribute> attribute, and is only allowed a single parent.

このエラーは、 relationship.single_parent フラグが使用され、同時に複数のオブジェクトがオブジェクトの”親”として割り当てられた場合に発生します。

次のようにマッピングします:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship(
        "A",
        single_parent=True,
        cascade="all, delete-orphan",
    )

インテントは、特定の A オブジェクトを一度に参照できる B オブジェクトは1つだけであることを示しています:

>>> b1 = B()
>>> b2 = B()
>>> a1 = A()
>>> b1.a = a1
>>> b2.a = a1
sqlalchemy.exc.InvalidRequestError: Instance <A at 0x7eff44359350> is
already associated with an instance of <class '__main__.B'> via its
B.a attribute, and is only allowed a single parent.

このエラーが予期せずに発生した場合、通常は For relationship <relationship>, delete-orphan cascade is normally configured only on the “one” side of a one-to-many relationship, and not on the “many” side of a many-to-one or many-to-many relationship. で説明されているエラーメッセージに応じて relationship.single_parent フラグが適用されたことが原因であり、この問題は実際には”delete-orphan”カスケード設定の誤解です。詳細については、このメッセージを参照してください。

relationship X will copy column Q to column P, which conflicts with relationship(s): ‘Y’

この警告は、2つ以上の関係がフラッシュ時に同じ列にデータを書き込むが、ORMにはこれらの関係を調整する手段がない場合を指します。具体的には、 relationship.back_populates を使用して2つの関係を互いに参照する必要がある、1つ以上の関係を relationship.viewonly で設定して書き込みの競合を防ぐ、場合によっては設定が完全に意図的であり、 relationship.overlaps を設定して各警告を沈黙させる、などの解決策が考えられます。

relationship.back_populates が欠落している典型的な例では、次のようにマッピングされます:

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    children = relationship("Child")


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))
    parent = relationship("Parent")

上記のマッピングでは警告が生成されます:

SAWarning: relationship 'Child.parent' will copy column parent.id to column child.parent_id,
which conflicts with relationship(s): 'Parent.children' (copies parent.id to child.parent_id).

Child.parentParent.children の関係が矛盾しているようです。解決策は relationship.back_populates を適用することです:

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    children = relationship("Child", back_populates="parent")


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))
    parent = relationship("Parent", back_populates="children")

“オーバーラップ”状態が意図的で解決できない、よりカスタマイズされた関係の場合、 relationship.overlaps パラメータは、警告が有効にならない関係の名前を指定することがあります。これは通常、それぞれのケースで関連する項目を制限するカスタムの relationship.primaryjoin 条件を含む、同じ基礎となるテーブルに対する2つ以上の関係に対して発生します:

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    c1 = relationship(
        "Child",
        primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 0)",
        backref="parent",
        overlaps="c2, parent",
    )
    c2 = relationship(
        "Child",
        primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 1)",
        overlaps="c1, parent",
    )


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))

    flag = Column(Integer)

上記では、ORMは Parent.c1Parent.c2Child.parent の間の重複が意図的であることを認識します。

Object cannot be converted to ‘persistent’ state, as this identity map is no longer valid.

New in version 1.4.26.

このメッセージは、ORMオブジェクトを生成する Result オブジェクトが、元の Session が閉じられた後、またはその Session.expunge_all() メソッドが呼び出された後に繰り返される場合に対応するために追加されました。 Session が一度にすべてのオブジェクトを削除すると、その Session で使用されている内部の identity map は新しいものに置き換えられ、元のものは破棄されます。消費されず、バッファもされていない Result オブジェクトは、破棄されたidentity mapへの参照を内部で保持します。したがって、 Result が消費されると、生成されるオブジェクトをその Session に関連付けることはできません。一般に、バッファもされていない Result オブジェクトを、それが作成されたトランザクションコンテキストの外で繰り返すことは推奨されないので、この配置は設計上のものです:

# context manager creates new Session
with Session(engine) as session_obj:
    result = sess.execute(select(User).where(User.id == 7))

# context manager is closed, so session_obj above is closed, identity
# map is replaced

# iterating the result object can't associate the object with the
# Session, raises this error.
user = result.first()

上記の状況は、 AsyncSession がsync-styleの Result を返す場合のように、ORM拡張の asyncio を使用する場合には 発生しません 。この場合、結果は文の実行時に事前にバッファされています。これは、2番目のEager Loaderが追加の await 呼び出しを必要とせずに呼び出すことを可能にするためです。

上記の状況で、通常の Session を使って、 asyncio 拡張が行うのと同じように結果を事前にバッファリングするには、次のように prebuffer_rows 実行オプションを使うことができます:

# context manager creates new Session
with Session(engine) as session_obj:
    # result internally pre-fetches all objects
    result = sess.execute(
        select(User).where(User.id == 7), execution_options={"prebuffer_rows": True}
    )

# context manager is closed, so session_obj above is closed, identity
# map is replaced

# pre-buffered objects are returned
user = result.first()

# however they are detached from the session, which has been closed
assert inspect(user).detached
assert inspect(user).session is None

上の例では、選択されたORMオブジェクトは完全に session_obj ブロック内で生成され、 session_obj に関連付けられ、 Result オブジェクト内に反復のためにバッファされます。ブロックの外では、 session_obj が閉じられ、これらのORMオブジェクトが削除されます。 Result オブジェクトを反復すると、これらのORMオブジェクトが生成されますが、元の Session がそれらを削除すると、それらは detached 状態で配信されます。

Note

上記の”pre-buffered”対”un-buffered” Result オブジェクトへの参照は、ORMが DBAPI から入ってくる生のデータベース行をORMオブジェクトに変換するプロセスを参照しています。これは、DBAPIからの保留中の結果を表す基礎となる cursor オブジェクト自体が、本質的にはバッファリングの下位層であるため、バッファリングされているか、されていないかを意味するものではありません。 cursor 結果自体のバッファリングのバックグラウンドについては、 Using Server Side Cursors (a.k.a. stream results) のセクションを参照してください。

Type annotation can’t be interpreted for Annotated Declarative Table form

SQLAlchemy 2.0では、新しい Annotated Declarative Table 宣言システムが導入されました。これは、実行時にクラス定義内の PEP 484 アノテーションからORMにマッピングされた属性情報を導出します。この形式の要件は、すべてのORMアノテーションが適切にアノテーションされるために Mapped と呼ばれる汎用コンテナを利用しなければならないことです。明示的な PEP 484 型付けアノテーションを含むレガシーSQLAlchemyマッピング(型付けサポートに legacy Mypy extension を使用するものなど)には、この汎用を含まない relationship() などのディレクティブが含まれる場合があります。

解決するには、2.0構文に完全に移行できるようになるまで、クラスに __allow_unmapped__ ブール属性を付けます。例については、 Migration to 2.0 Step Six - Add __allow_unmapped__ to explicitly typed ORM models の移行に関する注意を参照してください。

When transforming <cls> to a dataclass, attribute(s) originate from superclass <cls> which is not a dataclass.

この警告は、以下の例のように、 Declarative Dataclass Mapping で説明されているSQLAlchemy ORM Mapped Dataclasses機能を、それ自体がデータクラスとして宣言されていないミックスインクラスまたは抽象ベースと組み合わせて使用すると発生します:

from __future__ import annotations

import inspect
from typing import Optional
from uuid import uuid4

from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass


class Mixin:
    create_user: Mapped[int] = mapped_column()
    update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)


class Base(DeclarativeBase, MappedAsDataclass):
    pass


class User(Base, Mixin):
    __tablename__ = "sys_user"

    uid: Mapped[str] = mapped_column(
        String(50), init=False, default_factory=uuid4, primary_key=True
    )
    username: Mapped[str] = mapped_column()
    email: Mapped[str] = mapped_column()

上記では、 Mixin 自体は MappedAsDataclass から拡張されていないので、以下の警告が生成されます。

SADeprecationWarning: When transforming <class '__main__.User'> to a
dataclass, attribute(s) "create_user", "update_user" originates from
superclass <class
'__main__.Mixin'>, which is not a dataclass. This usage is deprecated and
will raise an error in SQLAlchemy 2.1. When declaring SQLAlchemy
Declarative Dataclasses, ensure that all mixin classes and other
superclasses which include attributes are also a subclass of
MappedAsDataclass.

この問題を解決するには、 MappedAsDataclassMixin のシグネチャに追加します:

class Mixin(MappedAsDataclass):
    create_user: Mapped[int] = mapped_column()
    update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)

Pythonの PEP 681 仕様は、自身がデータクラスではないデータクラスのスーパークラスで宣言された属性に対応していません。Pythonのデータクラスの動作では、以下の例のように、そのようなフィールドは無視されます:

from dataclasses import dataclass
from dataclasses import field
import inspect
from typing import Optional
from uuid import uuid4


class Mixin:
    create_user: int
    update_user: Optional[int] = field(default=None)


@dataclass
class User(Mixin):
    uid: str = field(init=False, default_factory=lambda: str(uuid4()))
    username: str
    password: str
    email: str

上記では、 User クラスはコンストラクタに create_user を含まず、 update_user をデータクラス属性として解釈しません。これは Mixin がデータクラスではないからです。

2.0シリーズのSQLAlchemyのデータクラス機能では、この動作は正しく行われません。代わりに、非データクラスのミックスインやスーパークラスの属性は、最終的なデータクラス設定の一部として扱われます。しかし、PyrightやMypyのような型チェッカーは、これらのフィールドをデータクラスコンストラクタの一部とはみなしません。なぜなら、これらは PEP 681 によって無視されるからです。これらの存在は他の点では曖昧であるため、SQLAlchemy 2.1では、データクラス階層内にSQLAlchemyがマップされた属性を持つミックスインクラスは、それ自体がデータクラスでなければならないことが要求されます。

Python dataclasses error encountered when creating dataclass for <classname>

MappedAsDataclass ミックスインクラスまたは registry.mapped_as_dataclass() デコレータを使用する場合、SQLAlchemyはPython標準ライブラリにある実際の Python dataclasses モジュールを使用して、データクラスの動作をターゲットクラスに適用します。このAPIには独自のエラーシナリオがあり、そのほとんどはユーザ定義クラスでの __init__() メソッドの構築を含みます。クラスで宣言された属性の順序は、 on superclasses と同様に、どのように __init__() メソッドが構築されるかを決定します。また、属性の編成方法や、init=Falsekw_only=True などのパラメータの使用方法には特定の規則があります。 SQLAlchemyはこれらの規則を制御または実装しません 。したがって、この種のエラーについては、 Python dataclasses のドキュメントを参照してください。特に inheritance に適用される規則に注意してください。

See also

Declarative Dataclass Mapping - SQLAlchemyデータクラスのドキュメント

Python dataclasses - python.orgのWebサイト

inheritance - python.orgのWebサイト

per-row ORM Bulk Update by Primary Key requires that records contain primary key values

このエラーは、 ORM Bulk UPDATE by Primary Key 機能を利用する際に、以下のような指定されたレコードに主キー値を指定しないと発生します:

>>> session.execute(
...     update(User).where(User.name == bindparam("u_name")),
...     [
...         {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"u_name": "patrick", "fullname": "Patrick Star"},
...     ],
... )

上記では、パラメータ辞書のリストの存在と、ORM対応のUPDATE文を実行するための Session の使用を組み合わせることで、自動的に主キーによるORMのバルクアップデートが使用されます。これは、パラメータ辞書が主キーの値を含むことを期待しています。例えば:

>>> session.execute(
...     update(User),
...     [
...         {"id": 1, "fullname": "Spongebob Squarepants"},
...         {"id": 3, "fullname": "Patrick Star"},
...         {"id": 5, "fullname": "Eugene H. Krabs"},
...     ],
... )

レコードごとの主キーの値を指定せずにUPDATE文を呼び出すには、 Session.connection() を使って現在の Connection を取得し、それを使ってを呼び出します:

>>> session.connection().execute(
...     update(User).where(User.name == bindparam("u_name")),
...     [
...         {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"u_name": "patrick", "fullname": "Patrick Star"},
...     ],
... )

AsyncIO Exceptions

AwaitRequired

SQLAlchemy非同期モードでは、データベースへの接続に非同期ドライバを使用する必要があります。このエラーは通常、互換性のない DBAPI で非同期バージョンのSQLAlchemyを使用しようとしたときに発生します。

MissingGreenlet

async DBAPI の呼び出しが、通常はSQLAlchemy AsyncIOプロキシクラスによって設定されたgreenlet spawnコンテキストの外で開始されました。通常、このエラーは、 await キーワードの使用を直接提供しない呼び出しパターンを使用して、予期しない場所でIOが試行されたときに発生します。ORMを使用する場合、これはほとんど常に lazy loading の使用によるものです。これは、正常に使用するために追加のステップや代替のローダーパターンがなければ、AsyncIOで直接サポートされません。

See also

Preventing Implicit IO when Using AsyncSession - この問題が発生する可能性のあるほとんどのORMシナリオと、遅延ロードシナリオで使用する特定のパターンを含む緩和方法をカバーしています。

No Inspection Available

AsyncConnection または AsyncEngine オブジェクトに対して inspect() 関数を直接使用することは、現在サポートされていません。これは、 Inspector オブジェクトの待機可能な形式がまだ存在しないためです。代わりに、 AsyncConnection オブジェクトの基礎となる AsyncConnection.sync_connection 属性を参照するような方法で、 inspect() 関数を使用してオブジェクトを取得することによって使用されます。その後、 Inspector は、 AsyncConnection.run_sync() メソッドと、必要な操作を実行するカスタム関数を使用して、”同期”呼び出しスタイルで使用されます:

async def async_main():
    async with engine.connect() as conn:
        tables = await conn.run_sync(
            lambda sync_conn: inspect(sync_conn).get_table_names()
        )

See also

Using the Inspector to inspect schema objects - asyncio拡張で inspect() を使う追加の例です。

Core Exception Classes

コア例外クラスについては Core Exceptions を参照してください。

ORM Exception Classes

ORM例外クラスについては ORM Exceptions を参照してください。

Legacy Exceptions

このセクションの例外は、現在のSQLAlchemyバージョンでは生成されませんが、例外メッセージのハイパーリンクに対応するためにここで説明します。

The <some function> in SQLAlchemy 2.0 will no longer <something>

SQLAlchemy 2.0は、コアコンポーネントとORMコンポーネントの両方におけるさまざまな主要なSQLAlchemy使用パターンの大きな変化を表しています。2.0リリースの目標は、SQLAlchemyの初期の開始以来の最も基本的な前提条件のいくつかを若干再調整し、コアコンポーネントとORMコンポーネントの間で大幅にミニマムで一貫性があり、より有能であることが期待される、新たに合理化された利用モデルを提供することです。

SQLAlchemy 2.0 - Major Migration Guide で紹介されたSQLAlchemy 2.0プロジェクトには、SQLAlchemyの1.4シリーズに統合された包括的な将来の互換性システムが含まれており、アプリケーションを完全に2.0互換に移行するために、アプリケーションに明確で曖昧さのない段階的なアップグレードパスを提供します。 RemovedIn20Warning 非推奨警告は、既存のコードベースのどの動作を変更する必要があるかについてのガイダンスを提供するために、このシステムの基礎にあります。この警告を有効にする方法の概要は SQLAlchemy 2.0 Deprecations Mode にあります。

See also

SQLAlchemy 2.0 - Major Migration Guide - 1.xシリーズからのアップグレードプロセスの概要と、SQLAlchemy 2.0の現在の目標と進捗状況。

SQLAlchemy 2.0 Deprecations Mode - SQLAlchemy 1.4の”2.0 deprecations mode”の使用方法に関するガイドライン。

Object is being merged into a Session along the backref cascade

このメッセージは、バージョン2.0で削除されたSQLAlchemyの”backref cascade”の動作を参照しています。これは、そのセッションに既に存在する別のオブジェクトが関連付けられた結果として、オブジェクトが Session に追加される動作を参照しています。この動作は役に立つというよりも混乱を招くことが示されているので、 relationship.cascade_backrefsbackref.cascade_backrefs `パラメータが追加されました。これを ``False` に設定して無効にすることができます。SQLAlchemy 2.0では”cascade backrefs”の動作は完全に削除されました。

古いバージョンのSQLAlchemyでは、現在 relationship.backref 文字列パラメータを使って設定されているbackrefに対して relationship.cascade_backrefsFalse に設定するには、まず cascade_backrefs() パラメータを渡せるようにしなければなりません。

あるいは、 Session.future パラメータに True を渡すことで、”future”モードで Session を 使用して、”cascade backrefs”動作全体を全面的にオフにすることもできます。

See also

cascade_backrefs behavior deprecated for removal in 2.0 - SQLAlchemy 2.0の変更のバックグラウンド

select() construct created in “legacy” mode; keyword arguments, etc.

select() 構文はSQLAlchemy 1.4の時点で更新され、SQLAlchemy 2.0の標準である新しい呼び出しスタイルをサポートします。1.4シリーズとの下位互換性のために、この構文は”legacy”スタイルと”new”スタイルの両方の引数を受け付けます。

“new”スタイルは、列とテーブルの式が select() 構文に位置的に渡されることを特徴としています。オブジェクトに対するその他の修飾子は、後続のメソッドチェーニングを使って渡さなければなりません:

# this is the way to do it going forward
stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid)

比較のために、 Select.where() のようなメソッドが追加される前のSQLAlchemyのレガシー形式の select() は、次のようになります:

# this is how it was documented in original SQLAlchemy versions
# many years ago
stmt = select([table1.c.myid], whereclause=table1.c.myid == table2.c.otherid)

あるいは、”where句”が位置的に渡されることもあります:

# this is also how it was documented in original SQLAlchemy versions
# many years ago
stmt = select([table1.c.myid], table1.c.myid == table2.c.otherid)

ここ数年、追加の”where句”やその他の引数が受け入れられていましたが、ほとんどの説明文書から削除されました。これにより、リストとして渡される列引数のリストとして最もよく知られた呼び出しスタイルになりましたが、それ以上の引数はありません:

# this is how it's been documented since around version 1.0 or so
stmt = select([table1.c.myid]).where(table1.c.myid == table2.c.otherid)

select() no longer accepts varied constructor arguments, columns are passed positionally の文書では、この変更を 2.0 Migration の観点から説明しています。

A bind was located via legacy bound metadata, but since future=True is set on this Session, this bind is ignored.

“バインドされたメタデータ”という概念はSQLAlchemy 1.4まで存在していましたが、SQLAlchemy 2.0では削除されました。

このエラーは、ORM Session のようなオブジェクトが特定のマップされたクラスを Engine に関連付けることを可能にする MetaData オブジェクトの MetaData.bind パラメータを参照します。SQLAlchemy 2.0では、 Session は各 Engine に直接リンクされていなければなりません。つまり、引数なしで Session または sessionmaker をインスタンス化し、 EngineMetaData に関連付けるのではありません:

engine = create_engine("sqlite://")
Session = sessionmaker()
metadata_obj = MetaData(bind=engine)
Base = declarative_base(metadata=metadata_obj)


class MyClass(Base): ...


session = Session()
session.add(MyClass())
session.commit()

代わりに、 Enginesessionmaker または Session に直接関連付ける必要があります。 MetaData オブジェクトはどのエンジンにも関連付けるべきではありません:

engine = create_engine("sqlite://")
Session = sessionmaker(engine)
Base = declarative_base()


class MyClass(Base): ...


session = Session()
session.add(MyClass())
session.commit()

SQLAlchemy 1.4では、この 2.0 style の振る舞いは、 Session.future フラグが sessionmaker または Session に設定されている場合に有効になります。

This Compiled object is not bound to any Engine or Connection

このエラーは、1.xバージョンにのみ存在するレガシーSQLAlchemyパターンである”バインドされたメタデータ”の概念に関連しています。この問題は、 Engine に関連付けられていないCore式オブジェクトから直接 Executable.execute() メソッドを呼び出したときに発生します:

metadata_obj = MetaData()
table = Table("t", metadata_obj, Column("q", Integer))

stmt = select(table)
result = stmt.execute()  # <--- raises

このロジックが想定しているのは、 MetaData オブジェクトが Engineバインド されているということです:

engine = create_engine("mysql+pymysql://user:pass@host/db")
metadata_obj = MetaData(bind=engine)

上記の場合、 Table から派生し、その MetaData から派生するステートメントは、暗黙的に指定された Engine を使用してステートメントを呼び出します。

バインドされたメタデータの概念は、 SQLAlchemy 2.0 には存在しないことに注意してください。文を呼び出す正しい方法は、 ConnectionConnection.execute() メソッドを使用することです:

with engine.connect() as conn:
    result = conn.execute(stmt)

ORMを使用する場合、 Session 経由で同様の機能を利用できます:

result = session.execute(stmt)

This connection is on an inactive transaction. Please rollback() fully before proceeding

このエラー条件は、バージョン1.4のSQLAlchemyに追加されたもので、SQLAlchemy 2.0には適用されません。このエラーは、 ConnectionConnection.begin() のようなメソッドを使ってトランザクションに入れられ、そのスコープ内でさらに”マーカー”トランザクションが作成される状態を指します。”マーカー”トランザクションは、その後 Transaction.rollback() を使ってロールバックされるか、 Transaction.close() を使って閉じられますが、外側のトランザクションはまだ”非アクティブ”な状態なので、ロールバックする必要があります。

The pattern looks like:

engine = create_engine(...)

connection = engine.connect()
transaction1 = connection.begin()

# this is a "sub" or "marker" transaction, a logical nesting
# structure based on "real" transaction transaction1
transaction2 = connection.begin()
transaction2.rollback()

# transaction1 is still present and needs explicit rollback,
# so this will raise
connection.execute(text("select 1"))

上の例では、 transaction2マーカー トランザクションであり、外部トランザクション内の論理的なネストを示しています。内部トランザクションはrollback()メソッドによってトランザクション全体をロールバックできますが、commit()メソッドは”マーカー”トランザクション自体のスコープを閉じる以外には何の効果もありません。 transaction2.rollback() の呼び出しは、基本的にデータベースレベルでロールバックされることを意味する transaction1非アクティブ化 する効果がありますが、トランザクションの一貫したネストパターンに対応するために存在しています。

正しい解決方法は、外部トランザクションも確実にロールバックされるようにすることです:

transaction1.rollback()

このパターンはCoreでは一般的に使用されていません。ORM内では、ORMの “論理的な” トランザクション構造の産物である同様の問題が発生する可能性があります。これは “This Session’s transaction has been rolled back due to a previous exception during flush.” (or similar) のFAQエントリで説明されています。

SQLAlchemy 2.0では”subtransaction”パターンが削除されたため、この特定のプログラミングパターンは使用できなくなり、このエラーメッセージが表示されなくなりました。