使用session.add或session.execute

时间:2018-07-10 11:52:52

标签: python sqlalchemy flask-sqlalchemy pytest

我正在尝试应用此处描述的配方:http://alexmic.net/flask-sqlalchemy-pytest/,以使用pytest来使用Flask-SQLAlchemy测试应用程序。

写作测试部分中指出:

  

请注意,我们可以像往常一样自由提交会话。之所以能够实现这一目标,是因为会话“加入了”由我们在会话固定装置中显式创建的连接所创建的外部事务,因此只有最外面的BEGIN / COMMIT对才起作用。

但是,如果我在测试中使用session.add方法,这对我不起作用。但是,当我改用session.execute时,它可以工作。

以下是显示问题的示例:

database.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

models.py

from database import db


class Employee(db.Model):
    __tablename__ = "employee"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)

test_employee.py

from models import Employee


def test_employee(session):
    my_employee = Employee()
    my_employee.name = "Toto"
    session.add(my_employee)
    # session.execute("INSERT INTO employee (id, name) VALUES (1, 'Tata');")
    session.commit()

conftest.py (根据示例略作改编)

import os

import pytest
from flask import Flask

from database import db as _db

TESTDB = 'test_utte.db'
TESTDB_PATH = "/tmp/{}".format(TESTDB)
TEST_DATABASE_URI = 'sqlite:///' + TESTDB_PATH


@pytest.fixture(scope='session')
def app(request):
    """Session-wide test `Flask` application."""
    settings_override = {
        'TESTING': True,
        'SQLALCHEMY_DATABASE_URI': TEST_DATABASE_URI,
        'SQLALCHEMY_TRACK_MODIFICATIONS': False  # Disabled to remove warning
    }
    app = Flask(__name__)
    app.config.update(settings_override)

    # Establish an application context before running the tests.
    ctx = app.app_context()
    ctx.push()

    def teardown():
        ctx.pop()

    request.addfinalizer(teardown)
    return app


@pytest.fixture(scope='session')
def db(app, request):
    """Session-wide test database."""
    if os.path.exists(TESTDB_PATH):
        os.unlink(TESTDB_PATH)

    def teardown():
        pass  # Commented to access database after test is run
        # _db.drop_all()
        # os.unlink(TESTDB_PATH)

    _db.init_app(app)
    _db.create_all()

    request.addfinalizer(teardown)
    return _db


@pytest.fixture(scope='function')
def session(db, request):
    """Creates a new database session for a test."""
    db.engine.echo = True
    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()

    request.addfinalizer(teardown)
    return session

运行pytest -s给出以下日志:

test_employee.py 2018-07-10 13:22:48,645 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-07-10 13:22:48,648 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-07-10 13:22:48,650 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (name) VALUES (?)
2018-07-10 13:22:48,650 INFO sqlalchemy.engine.base.Engine ('Toto',)
2018-07-10 13:22:48,651 INFO sqlalchemy.engine.base.Engine COMMIT
.2018-07-10 13:22:48,657 INFO sqlalchemy.engine.base.Engine ROLLBACK

如果我在数据库中签入,则条目在此处

sqlite> select * from employee;
1|Toto

相反,如果我将 test_employee.py 替换为session.execute部分:

from models import Employee


def test_employee(session):
    # my_employee = Employee()
    # my_employee.name = "Toto"
    # session.add(my_employee)
    session.execute("INSERT INTO employee (id, name) VALUES (1, 'Tata');")
    session.commit()

我得到以下日志:

test_employee.py 2018-07-10 13:28:27,093 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-07-10 13:28:27,095 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (id, name) VALUES (1, 'Tata');
2018-07-10 13:28:27,096 INFO sqlalchemy.engine.base.Engine ()
.2018-07-10 13:28:27,098 INFO sqlalchemy.engine.base.Engine ROLLBACK

并且该条目不在数据库中:

sqlite> select * from employee;
sqlite> 

最后,如果我同时参加了测试:

from models import Employee

def test_employee(session):
    my_employee = Employee()
    my_employee.name = "Toto"
    session.add(my_employee)
    session.execute("INSERT INTO employee (id, name) VALUES (1, 'Tata');")
    session.commit()

然后我有类似的情况,只有session.execute

test_employee.py 2018-07-10 13:31:57,179 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-07-10 13:31:57,180 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (id, name) VALUES (1, 'Tata');
2018-07-10 13:31:57,181 INFO sqlalchemy.engine.base.Engine ()
2018-07-10 13:31:57,182 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (name) VALUES (?)
2018-07-10 13:31:57,182 INFO sqlalchemy.engine.base.Engine ('Toto',)
.2018-07-10 13:31:57,183 INFO sqlalchemy.engine.base.Engine ROLLBACK

该表仍然为空:

sqlite> select * from employee;
sqlite> 

我的猜测是这与我的第一个示例中的日志中显示的内部 BEGIN / COMMIT 有关,但我并不真正理解为什么它在这里或为什么它覆盖了最外面的 BEGIN / ROLLBACK

2 个答案:

答案 0 :(得分:1)

这是一个非常棘手的问题,我想知道这是否与在sqlite中实现session.execute有关。我从未在Postgres中看到过类似的东西。

虽然这不能解决问题的根源,但我想知道使用pytest-flask-sqlalchemy-transactions是否可以解决您的问题。该插件旨在解决这个确切的用例,并公开了一个db_session固定装置,可以满足您的需求。

您需要设置一个_db固定装置,以使插件可以访问您的数据库:

@pytest.fixture
def _db(db):
    return db

查看the docs以获得安装说明,并告诉我它是否适合您。它尚未经过sqlite的广泛测试,因此至少可以帮助您确定这是否是sqlite特有的问题。

答案 1 :(得分:1)

由于jeancochrane's answer,我设置了一个新的virtualenv来安装pytest-flask-sqlalchemy-transactions,却发现我什至无法重现问题!

经过搜索和比较后,我发现问题出在我最初使用带有Flask-SQLAlchemy 2.1.0的venv的事实。

升级到下一版本(Flask-SQLAlchemy 2.2.0)时,问题消失了。

changelog for 2.2.0提到:

  
      
  • 允许监听db.session上的SQLAlchemy事件
  •   

也许是引起此问题的原因?我不知道某些Flask-SQLAlchemy开发人员是否能够回答这个问题?