我正在为 pytest 配置一个用于创建烧瓶应用程序实例的夹具。我的应用是使用应用程序工厂模式创建的。我正处于将其连接到数据库的阶段,并且努力理解两种模式之间的区别。
# project/__init__.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app_settings = os.getenv('APP_SETTINGS')
app.config.from_object(app_settings)
db.init_app(app)
[blueprint code]
return app
在我的赛程中,我想我理解需要:
- 设置过程中
db.create_all()
:创建我的表 - 拆解期间
db.drop_all()
:测试后清理数据库 - 拆解过程中
db.session.remove()
:在测试中频繁访问数据库时,避免在 postgres 上出现一些奇怪的锁定
第一个设置(灵感来自米格尔·格林伯格的书(对我来说很有意义:
import pytest
from project import create_app, db
@pytest.fixture
def app():
app = create_app()
with app.app_context():
db.create_all()
yield app
db.session.remove()
db.drop_all()
它还与我在交互式会话中获得的行为相匹配,我需要激活/推送app_context
来绑定数据库:
Python 3.6.1 (default, Jun 21 2017, 18:45:41)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from project import create_app, db
>>> app = create_app()
>>> db
<SQLAlchemy engine=None>
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> db.create_all()
>>> db
<SQLAlchemy engine='postgres://postgres:postgres@users-db:5432/users_dev'>
第二个设置(受 testdriven.io 启发(也可以在pytest中工作,但我不知道为什么:
import pytest
from project import create_app, db
@pytest.fixture
def app():
app = create_app()
db.create_all()
db.session.commit() # fail when this is removed
yield app
db.session.remove()
db.drop_all()
实际上,如果我尝试在交互式会话中执行相同的操作,则会出现错误:
Python 3.6.1 (default, Jun 21 2017, 18:45:41)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from project import create_app, db
>>> app = create_app()
>>> db.create_all()
[...] timeError: application not registered on db instance and no application bound to current context
我尝试在没有db.session.commit()
的情况下运行该装置,认为默认情况下我可能处于应用程序上下文中(类似于我在第一个灯具中对with app_context()
所做的(。但是如果我删除它,它会失败。
第一个问题是第二个设置(通过testdriven.io
(没有使用应用程序工厂模式,它们显式实例化数据库并绑定应用程序(例如,db = SQLAlchemy(app)
与db = SQLAlchemy()
以及create_app()
中的更高db.init(app)
(。如果您使用的是应用程序工厂模式,则在执行db.create_all()
后,您将在交互式会话中看到错误,保留/删除db.session.commit()
无论哪种方式都无济于事。
我有一个暗示,您每次尝试都使用两种不同的from project import create_app, db
,并将应用程序工厂模式用于交互式 shell。
无论如何,你实际上是在问两个问题。
1( 为什么在使用应用程序工厂模式时需要推送应用程序上下文才能运行db.create_all()
?
如果您查看SQLAlchemy
中的__init__
方法,您会注意到您可以传递一个应用程序,在这种情况下,该应用程序将绑定到 SQLAlchemy 对象,self.app = app
。但是,由于使用的是应用程序工厂模式,因此永远不会显式绑定该应用,即使在运行db.init_app(app)
之后也是如此。现在看create_all()
,它需要一个可选的app
,你没有通过,所以当我们进入get_app
时,我们跳过reference_app
因为它是None
,我们回顾current_app
,它查看Flask的应用程序上下文(见from flask import current_app
(,如果你没有推送上下文,这也会被None
, 最后我们检查是否有self.app
,但这也是None
,因为我们使用的是应用程序工厂模式,因此application not registered on db instance and no application bound to current context
错误。
2( 为什么在 SQLAlchemy 实例化期间绑定应用程序(例如,db = SQLAlchemy(app)
(时,我需要在运行db.create_all
后显式运行db.session.commit()
?
我似乎无法重现此错误,我在下面添加了一个代码块来向您展示我正在使用的内容,我从testdriven.io
站点获取了这些片段。但是,您不需要db.session.commit()
,使用应用程序工厂模式和在应用程序(例如,db = SQLAlchemy(app)
(中实例化 SQLAlchemy 之间的唯一区别是,对于前者,您需要在create_all(app)
中传入应用程序或推送应用程序上下文。
import pytest
import os
import datetime
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
@pytest.fixture
def app():
app = create_app()
db.create_all()
# db.session.commit() # Try this with and without this line
yield app
db.session.remove()
db.drop_all()
引用:
https://github.com/mitsuhiko/flask-sqlalchemy/blob/d71afea650e0186348d81f02cca5181ed7c466e9/flask_sqlalchemy/初始化.py
http://flask-sqlalchemy.pocoo.org/2.1/contexts/