7

我正在使用Flask-Testing说:

另一个问题是 Flask-SQLAlchemy 还会在每个请求结束时删除会话实例(任何使用带有 scoped_session 的 SQLAlchemy 的线程安全应用程序也是如此)。因此,每次调用 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.nameisbob和 not 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
4

3 回答 3

10

我很确定混淆来自 SQLAlchemy 中的会话是scoped的事实,这意味着每个请求处理程序都会创建和销毁自己的会话。

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

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

更新:我挖了一下,弄清楚了这件事。

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

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

作为旁注,请记住,在您调用时注册的函数before_requestafter_request不会被调用client.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 版本来说并不准确。

我希望这有帮助!

于 2013-10-20T02:10:29.150 回答
0

FlaskClientFlask-SQLAlchemyFlask版本 0.9shutdown_session起就使用请求上下文。这就是为什么在测试客户端调用之后什么都没有发生的原因,因为应用程序上下文甚至在测试之前就开始了,并且会在.app.teardown_appcontextflask.ext.testing.TestCasesetUptearDown

于 2013-10-18T08:52:59.973 回答
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
于 2016-01-06T07:12:54.423 回答