我有一个应用程序,可以使用 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 数据库中创建表。这意味着我无法使用固定架构设置声明性表。因为我们不知道模式名称是什么,结果是表格所属的模式。
答案 0 :(得分:2)
"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']
会话非常聪明,可以在将打开的事务作为绑定传递给现有连接时自动使用嵌套事务(保存点)。如果您的会话必须在测试中回滚,请参阅链接文档底部的“支持回滚测试”主题。
由于您使用的是flask和flask-sqlalchemy,因此您可能需要对此进行调整以适应您的测试环境。 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()