sqlalchemy会话未在烧瓶测试中正确删除

时间:2013-10-16 05:11:09

标签: python flask flask-sqlalchemy

我正在使用Flask-Testing说:

  

另一个问题是Flask-SQLAlchemy也删除了会话   每个请求结束时的实例(如同任何线程安全一样)   使用SQLAlchemy和scoped_session的应用程序。因此   会话被清除以及每次添加到它的任何对象   调用client.get()或其他客户端方法。

但是,我没有看到。此测试失败:

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.testing import TestCase

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
@app.route('/')
def index():
  print 'before request:', `db.session`
  u = db.session.query(User).first()
  u.name = 'bob'
  return ''

class User(db.Model):
  id = db.Column(db.Integer, primary_key=True)
  name = db.Column(db.String)

class SessionTest(TestCase):

  def create_app(self):
    return app

  def test_remove(self):
    db.drop_all()
    db.create_all()

    u = User()
    u.name = 'joe'
    db.session.add(u)
    db.session.commit()
    client = app.test_client()
    client.get('/')
    print 'after request:', `db.session`
    print u.name
    assert u not in db.session

(使用$ nosetests test_file.py运行以查看其实际效果。)

标准输出:

-------------------- >> begin captured stdout << ---------------------
before request: <sqlalchemy.orm.scoping.ScopedSession object at 0x10224c610>
after request: <sqlalchemy.orm.scoping.ScopedSession object at 0x10224c610>
bob

--------------------- >> end captured stdout << ----------------------

根据文档,用户u不应该在获取请求后进入会话,但它是!有人知道为什么会这样吗?

此外,u.namebob而不是joe,即使请求从未提交过! (所以我确信它是同一个会议。)

记录,

$ pip freeze | grep Flask
Flask==0.10.1
Flask-Bcrypt==0.5.2
Flask-DebugToolbar==0.8.0
Flask-Failsafe==0.1
Flask-SQLAlchemy==0.16
Flask-Script==0.6.2
Flask-Testing==0.4
Flask-Uploads==0.1.3
Flask-WTF==0.8

3 个答案:

答案 0 :(得分:10)

我很确定混淆来自于SQLAlchemy中的会话作用域,这意味着每个请求处理程序都会创建并销毁自己的会话。

这是必要的,因为Web服务器可以是多线程的,因此可以同时提供多个请求,每个请求使用不同的数据库会话。

出于这个原因,您在请求上下文之外使用的会话可能与处理'/'路由的视图函数获取的会话相同,然后在最后销毁。

更新:我挖了一下,想出了这件事。

Flask-SQLAlchemy在app.teardown_appcontext上安装了一个钩子,这里是它调用db.session.remove()的地方。

测试环境不能完全复制真实请求的环境,因为它不会推送/弹出应用程序上下文。因此,在请求结束时永远不会删除会话。

作为旁注,请注意,当您致电before_request时,注册after_requestclient.get()的功能也不会被调用。

您可以通过对测试进行少量更改来强制执行应用程序上下文推送和弹出:

def test_remove(self):
  db.drop_all()
  db.create_all()

  u = User()
  u.name = 'joe'
  db.session.add(u)
  db.session.commit()
  with app.app_context():
      client = app.test_client()
      client.get('/')
  print 'after request:', `db.session`
  print u.name
  assert u not in db.session

通过此更改,测试通过。

Flask-Testing的文档似乎是错误的或更可能过时。也许事情就像他们在某些时候描述的那样,但这对于当前的Flask和Flask-SQLAlchemy版本来说并不准确。

我希望这有帮助!

答案 1 :(得分:0)

FlaskClient适用于请求上下文,而 Flask-SQLAlchemy shutdown_session上调用它app.teardown_appcontext,因为 Flask 版本0.9。这就是测试客户端调用之后没有任何反应的原因,即使在测试flask.ext.testing.TestCase之前setUp启动了应用程序上下文,并且会在tearDown之后关闭。

答案 2 :(得分:0)

尝试使用Flask-Manage运行我的测试时遇到了同样的问题。在单独的线程中运行测试解决了这个问题。

import threading
# some code omited
runner = unittest.TextTestRunner(verbosity=2)
t = threading.Thread(target=runner.run, args=[test_suite])
t.start()
t.join()
# other code omited