我在SQLAlchemy数据库引擎/连接/会话周围创建了一个“manager”对象:
Base = declarative_base()
class Manager(object):
def __init__(self, connection: str = 'sqlite://'):
self.engine = create_engine(connection, echo=True)
Base.metadata.create_all(self.engine)
self.sessionmaker = sessionmaker(bind=self.engine)
self.session = scoped_session(self.sessionmaker)
def do_db_stuff(self):
self.session.query(Whatever).all()
def ensure_thing(self):
thing = Thing()
self.session.add(thing)
self.session.commit()
我想创建两个py.test fixtures:一个用于实例化管理器,另一个用于在可能调用commit的测试中包装和回滚事务。 This is the pattern我试图效仿,但没有成功:
@pytest.fixture(scope='session')
def manager():
m = Manager()
return m
@pytest.fixture(scope='function')
def manager_session(manager):
connection = manager.session.connection()
transaction = connection.begin()
yield manager
manager.session.close()
transaction.rollback()
connection.close()
不幸的是,管理员创建的对象即使在被transaction.rollback()
调用之后也被上面所包围。
围绕现有会话包装事务的正确方法是什么?
编辑:
另一种不同的尝试:
@pytest.fixture(scope='function')
def manager_session(manager):
connection = manager.engine.connect()
transaction = connection.begin()
manager.sessionmaker.configure(bind=connection)
yield manager
manager.session.close()
transaction.rollback()
编辑2:
第三次尝试似乎有效,但Ilja Everilä's answer below中提到的警告说线程代码会引起麻烦。
@pytest.fixture(scope='session')
def manager():
return Manager()
@pytest.fixture(scope='function')
def manager_transaction(manager):
connection = manager.engine.connect()
transaction = connection.begin()
manager.session_maker.configure(bind=connection)
yield manager
manager.session_maker.configure(bind=manager.engine)
manager.session.remove()
transaction.rollback()
connection.close()
答案 0 :(得分:1)
第一次尝试失败,因为会话实际上是在控制连接及其事务。您可以通过查看生成的日志记录来验证。当您调用manager.session.connection()
并且之后对begin()
的显式调用是返回正在进行的事务对象的无操作时,会话开始一个隐式事务。因此,当您在管理器方法中提交时,您将提交实际内容,而现在过时的事务对象在您回滚时不会执行任何操作。
如果使用内存中的SQLite数据库,第二次尝试对我来说就像一样,但如果您的实际代码与您提供的内容略有不同,则无效。您将创建的连接设置为self.sessionmaker
上的绑定,而不是已创建的scoped session registry self.session
中的会话,因此如果您在配置制造商之前以任何方式触摸了会话注册表,您实际上已使用引擎创建了一个会话作为当前线程中的绑定:
In [7]: m = Manager()
In [8]: m.session.bind
Out[8]: Engine(sqlite://)
In [9]: connection = m.engine.connect()
In [10]: transaction = connection.begin()
2017-08-28 14:24:02,584 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
In [11]: m.sessionmaker.configure(bind=connection)
In [12]: m.session.bind
Out[12]: Engine(sqlite://)
因此,除了配置sessionmaker之外,还应确保之前未在注册表中注册会话。另请注意,如果您有使用线程的代码,注册表将共享它们之间的连接,这将导致麻烦。