2016-09-08 17 views
1

SQLAlchemyのautomap拡張を使用して既存のデータベースのORMを生成しようとしていますが、InvalidRequestError例外が発生しています(「インスタンスはリフレッシュできません。完全なプライマリキーです。 ")タイムスタンプと外部キーからなる複合プライマリキーを使用するテーブルに挿入しようとするたびに発生します。SQLAlchemy automapにORMを挿入するとInvalidRequestErrorが発生する

これは、次のトレースバックを与える実行
from sqlalchemy import create_engine, func, select 
from sqlalchemy.orm import sessionmaker 
from sqlalchemy.sql.expression import text 
from sqlalchemy.ext.automap import automap_base 

db_schema_cmds = [ 
    '''CREATE TABLE users 
    (
     u_id INTEGER NOT NULL, 
     name TEXT NOT NULL, 
     CONSTRAINT Key1 PRIMARY KEY (u_id) 
    );''', 
    '''CREATE TABLE posts 
    (
     timestamp TEXT NOT NULL, 
     text TEXT NOT NULL, 
     u_id INTEGER NOT NULL, 
     CONSTRAINT Key2 PRIMARY KEY (timestamp,u_id), 
     CONSTRAINT users_have_posts FOREIGN KEY (u_id) REFERENCES users (u_id) ON DELETE CASCADE 
    );'''] 

# Create a new in-memory SQLite DB and execute the schema SQL commands. 
db_engine = create_engine('sqlite://') 
with db_engine.connect() as db_conn: 
    for cmd in db_schema_cmds: 
     db_conn.execute(text(cmd)) 

# Use automap to reflect the DB schema and generate ORM classes. 
Base = automap_base() 
Base.prepare(db_engine, reflect=True) 

# Create aliases for the table classes generated. 
User = Base.classes.users 
Post = Base.classes.posts 

session_factory = sessionmaker() 
session_factory.configure(bind=db_engine) 

# Add a user and a post to the DB. 
session = session_factory() 
new_user = User(name="John") 
session.add(new_user) 
session.commit() 
new_post = Post(users=new_user, text='this is a test', timestamp=func.now()) 
session.add(new_post) 
session.commit() 

# Verify that the insertion worked. 
new_user_id = session.execute(select([User])).fetchone()['u_id'] 
new_post_fk_user_id = session.execute(select([Post])).fetchone()['u_id'] 
assert new_user_id == new_post_fk_user_id 

session.close() 

:ここ

は、問題を再現するいくつかの最小限のコード例です

Traceback (most recent call last): 
    File "reproduce_InvalidRequestError.py", line 67, in <module> 
    session.commit() 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\session.py", line 801, in commit 
    self.transaction.commit() 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\session.py", line 392, in commit 
    self._prepare_impl() 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\session.py", line 372, in _prepare_impl 
    self.session.flush() 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\session.py", line 2019, in flush 
    self._flush(objects) 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\session.py", line 2137, in _flush 
    transaction.rollback(_capture_exception=True) 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\util\langhelpers.py", line 60, in __exit__ 
    compat.reraise(exc_type, exc_value, exc_tb) 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\util\compat.py", line 186, in reraise 
    raise value 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\session.py", line 2107, in _flush 
    flush_context.finalize_flush_changes() 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 395, in finalize_flush_changes 
    self.session._register_newly_persistent(other) 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\session.py", line 1510, in _register_newly_persistent 
    instance_key = mapper._identity_key_from_state(state) 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\mapper.py", line 2417, in _identity_key_from_state 
    for col in self.primary_key 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\mapper.py", line 2417, in <listcomp> 
    for col in self.primary_key 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\attributes.py", line 578, in get 
    value = state._load_expired(state, passive) 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\state.py", line 474, in _load_expired 
    self.manager.deferred_scalar_loader(self, toload) 
    File "C:\Python\Python35\lib\site-packages\sqlalchemy\orm\loading.py", line 647, in load_scalar_attributes 
    "contain a full primary key." % state_str(state)) 
sqlalchemy.exc.InvalidRequestError: Instance <posts at 0x45d45f8> cannot be refreshed - it's not persistent and does not contain a full primary key. 

私はcreate_engineコールにecho=Trueパラメータを追加した場合、私はそれを見ています挿入のために次のSQLを生成します。このSQLは、SQLiteのDBブラウザで実行するとうまく動作します。

INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
INFO sqlalchemy.engine.base.Engine SELECT users.u_id AS users_u_id, users.name AS users_name FROM users WHERE users.u_id = ? 
INFO sqlalchemy.engine.base.Engine (1,) 
INFO sqlalchemy.engine.base.Engine INSERT INTO posts (timestamp, text, u_id) VALUES (CURRENT_TIMESTAMP, ?, ?) 
INFO sqlalchemy.engine.base.Engine ('this is a test', 1) 
INFO sqlalchemy.engine.base.Engine ROLLBACK 

私もPost()からusersパラメータを削除しようとしたし、代わりにsession.add(new_post)を呼び出す前に、ラインnew_user.posts_collection.append(new_post)を追加し、それが生成され、同じSQLをもたらし、同じエラーが発生しました。

コンポジットキーを新しい整数型PK列に置き換えると、すべて正常に動作します。 (ない理想的なソリューションしかし、私はautomapを使用している理由としては、DBのスキーマことを修正する必要がないことが好ましいですので、 DBを既存のを反映するためである。)私は、同様の質問、SQLAlchemy InvalidRequestError when using composite foreign keysを見つけ

、しかし、テーブルORMクラスでの継承の使用に関連していたようで、ORMテーブルクラスを定義するためには、DBを反映させるのではなく、ORMテーブルクラスを定義する必要がありました。

編集:私はもともと、この問題は複合主キーに外部キーが含まれているという事実に関連していると想定していました。受け入れられた答えは、外部キーが実際に問題の原因となっていないことを示しています。

+0

は完璧[MCVE](HTTP提供いただき、ありがとうございますtimestampを提供://をstackoverflow.com/help/mcve)! –

答えて

1

実際には、外部キーを持つ複合プライマリキーではなく、func.now()がプライマリキーの一部であるtimestampとして渡されます。値はSQLAlchemyには知られていないので、データベースに挿入中に値が生成されるため、ポストフェッチは実行できません。何を取り出すのか分かりません。問題のDBがRETURNINGなどをサポートしている場合は、これを行うことができます。この正確な状況については、triggered columnsの注記を参照してください。主キー値のSQLの事前実行については、Defaults/SQL Expressionsも参照してください。

整数代理プライマリキーで動作するのは、SQLiteにfetching the last inserted row id(整数主キー列)のメカニズムがあり、SQLAlchemyが使用できるためです。あなたは他のソリューションは、反射時にtimestamp列を上書きすると、デフォルトとしてfunc.now()を提供することでPythonの

In [8]: new_post = Post(users=new_user, text='this is a test', 
    ...:     timestamp=datetime.utcnow()) 
    ...: session.add(new_post) 
    ...: session.commit() 
    ...: 

を生成したタイムスタンプを使用することができ、これを改善するために

。これにより、事前実行がfunc.now()にトリガーされます。デフォルトでは

...: # Use automap to reflect the DB schema and generate ORM classes. 
    ...: Base = automap_base() 
    ...: 
    ...: # Override timestamp column before reflection 
    ...: class Post(Base): 
    ...:  __tablename__ = 'posts' 
    ...:  timestamp = Column(Text, nullable=False, primary_key=True, 
    ...:      default=func.now()) 
    ...: 
    ...: Base.prepare(db_engine, reflect=True) 
    ...: 
    ...: # Create aliases for the table classes generated. 
    ...: User = Base.classes.users 
    ...: # Post has already been declared 
    ...: #Post = Base.classes.posts 

代わりに、あなたがする必要はありません(とすべきでない)新しいインスタンスを作成するときに

In [6]: new_post = Post(users=new_user, text='this is a test') 
    ...: session.add(new_post) 
    ...: session.commit() 
    ...: 
+0

すばらしい説明、ありがとう!外部キーが原因であるとの推測を削除するために投稿を編集します。 –

関連する問題