有没有办法将会话对象绑定到SQLAlchemy中的MetaData.create_all方法?

时间:2016-09-19 06:01:30

标签: python postgresql sqlalchemy

我有一个应用程序,可以使用 SQLAlchemy 按需创建表。更确切地说, Flask-SQLAlchemy PostgreSQL 作为数据库。

为此,(1)我创建一个PostgreSQL架构来保存新表:

# extra checks on the schema name before I execute it.

statement = 'CREATE SCHEMA IF NOT EXISTS {}'.format(schema_name)
database.session.execute(statement)
database.session.commit()

之后,(2)我将PostgreSQL search_path值更改为我创建的架构。

(3)然后我列出了我想在数据库中创建的表,并将其传递给create_all MetaData的方法:

metadata.create_all(database.engine, tables=list_of_tables)

SQLAlchemy 进行查询以检查这些表是否已存在(在新架构上),然后将CREATE TABLE语句发送到数据库。

表格是在所需的架构中正确创建的,一切正常。

当我将所有这些任务包装在嵌套事务(使用PostgreSQL SAVEPOINTs)中用于测试目的时,我的问题就开始了,以便回滚当前会话中的所有内容测试结束。我正在使用Supporting Tests with Rollbacks中的示例,来自SQLAlchemy文档。

模式创建发生在嵌套事务中,但MetaData.crate_all在另一个事务中创建作业,并且无法在数据库中找到新模式,因为新模式在包装会话中只是活动而不是在数据库中物理创建。

这会使测试失败并显示(psycopg2.ProgrammingError) no schema has been selected to create in ...

我想的解决方案是使用包装的会话逐个创建表,或者弄清楚如何将create_all绑定到包装的会话。

更新

为了澄清我的问题,正如我在顶部所说,应用程序必须在移动中的新架构内的 demand 数据库中创建表。这意味着我无法使用固定架构设置声明性表。因为我们不知道模式名称是什么,结果是表格所属的模式。

1 个答案:

答案 0 :(得分:2)

在这种情况下,来自SQLAlchemy文档的

"Joining a Session into an External Transaction (such as for test suites)"是一个很好的起点。我会稍微重构一下你的方法:在从engine获取的连接的事务中创建模式。然后将测试会话加入到所述连接,执行测试和回滚。这是一个简单的例子:

In [2]: engine
Out[2]: Engine(postgresql://baz@localhost/sopython)

In [3]: conn = engine.connect()

In [4]: trans = conn.begin()

In [5]: class Foo(Base):
   ...:     __tablename__ = 'foo'
   ...:     __table_args__ = {'schema': 'bar'}
   ...:     id = Column(Integer, primary_key=True)

In [7]: from sqlalchemy.schema import CreateSchema

In [8]: conn.execute(CreateSchema('bar'))
Out[8]: <sqlalchemy.engine.result.ResultProxy at 0x7f8fd4084d68>

In [9]: Base.metadata.create_all(conn)  # Explicitly pass `conn` as bind!

In [10]: session = Session(bind=conn)  # This here joins the `session` to the
    ...:                               # external transaction.
    ...:

In [11]: session.query(Foo).all()
Out[11]: []

In [12]: trans.rollback()  # Undo everything.

In [13]: session.query(Foo).all()  # Table should not exist anymore.
---------------------------------------------------------------------------
ProgrammingError                          Traceback (most recent call last)
  ...
ProgrammingError: (psycopg2.ProgrammingError) relation "bar.foo" does not exist
LINE 2: FROM bar.foo
             ^
 [SQL: 'SELECT bar.foo.id AS bar_foo_id \nFROM bar.foo']

In [14]: Base.metadata.create_all()  # Uses metadata.engine implicitly,
    ...:                             # acquires a new connection etc, but
    ...:                             # the schema is now gone.
    ...:
---------------------------------------------------------------------------
ProgrammingError                          Traceback (most recent call last)
  ...
ProgrammingError: (psycopg2.ProgrammingError) schema "bar" does not exist
 [SQL: '\nCREATE TABLE bar.foo (\n\tid SERIAL NOT NULL, \n\tPRIMARY KEY (id)\n)\n\n']

会话非常聪明,可以在将打开的事务作为绑定传递给现有连接时自动使用嵌套事务(保存点)。如果您的会话必须在测试中回滚,请参阅链接文档底部的“支持回滚测试”主题。

由于您使用的是,因此您可能需要对此进行调整以适应您的测试环境。 Alex Michael的This post有一个例子:flask-sqlalchemy和pytest。它的要点是在安装过程中创建一个新的连接会话:

connection = db.engine.connect()
transaction = connection.begin()

options = dict(bind=connection, binds={})
session = db.create_scoped_session(options=options)

db.session = session

并在拆解期间执行所需的回滚,关闭等:

def teardown():
    transaction.rollback()
    connection.close()
    session.remove()