如何使用 SQLAlchemy 设置 Flask 应用程序进行测试?

发布于 2024-10-17 23:12:05 字数 515 浏览 0 评论 0原文

Flask 中的常见做法似乎是这样开始的:

from flask import Flask
from flaskext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
SQLALCHEMY_DATABASE_URI = 'something'
app.config.from_object(__name__)
db = SQLAlchemy(app)

然后在任何地方导入并使用 appdb 。但是,当您像这样创建 db 时,它会从应用程序中获取配置,并且似乎该配置一旦发生就无法被覆盖。 Flask 的网站上有一些关于创建应用程序工厂的页面,但不清楚如果我这样做的话,我如何能够在任何地方仍然使用 appdb

如何编写脚本来使用不同的数据库测试我的 Flask 应用程序?我应该如何构建我的应用程序才能实现这一点?我必须使用模块吗?

It seems common practice in Flask to start like this:

from flask import Flask
from flaskext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
SQLALCHEMY_DATABASE_URI = 'something'
app.config.from_object(__name__)
db = SQLAlchemy(app)

And then import and use app and db everywhere. But when you create db like this, it grabs configuration from the app, and it seems that this configuration can't ever be overridden once it happens. There are some pages on Flask's website about making application factories, but it's not clear how I would be able to still use app and db everywhere if I did that.

How do I write a script to test my Flask application with a different database? How should I structure my application to make this possible? Do I have to use modules ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

攒眉千度 2024-10-24 23:12:05

您使用环境变量的直觉是正确的。但是,使用错误的数据库运行单元测试存在一些危险。另外,您可能不希望在每个请求以及想要使用 db 的任何地方都使用 connect_db。您可以使用显式设置的配置目录和环境变量。这是迄今为止我想出的最好的办法。

run.py
shell.py

config/__init__.py
config/test.py
config/postgres.py
...

main/__init__.py
main/someapp/__init__.py
main/someapp/models.py

...
main/tests/__init__.py
main/tests/testutils.py

所以,配置文件可能是:

# config/test.py
SQLALCHEMY_DATABASE_URI = "sqlite://"

所以

# config/postgres.py
SQLALCHEMY_DATABASE_URI = 'postgresql://user:pw@localhost/somedb'

,我可以在我的基本测试用例中显式设置数据库:

import os
from flask.ext.testing import TestCase

os.environ["DIAG_CONFIG_MODULE"] = "config.test"
from main import app, db


class SQLAlchemyTest(TestCase):

    def create_app(self):
        return app

    def setUp(self):
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()

然后,对我来说,main/__init__.py

import os

from flask import Flask, render_template, g
from flask.ext.sqlalchemy import SQLAlchemy

# by default, let's use a DB we don't care about
# but, we can override if we want
config_obj = os.environ.get("DIAG_CONFIG_MODULE", "config.test")
app = Flask(__name__)
app.config.from_object(config_obj)
db = SQLAlchemy(app)

@app.before_request
def before_request():
    g.db = db
    g.app = app

# ...
@app.route('/', methods=['GET'])
def get():
    return render_template('home.html')
# ...    
from main.someapp.api import mod as someappmod
app.register_blueprint(someappmod)

然后,在其他文件中,我知道我想运行什么配置,可能:

# run.py
import os
os.environ["DIAG_CONFIG_MODULE"] = "config.postgres"
from main import app
app.run(debug=True)

也许

# shell.py
import os
os.environ["DIAG_CONFIG_MODULE"] = "config.postgres"

from main import app, db
from main.symdiag.models import *
from main.auth.models import *
print sorted(k for k in locals().keys() if not k.startswith("_"))
import IPython
IPython.embed()

..到目前为止最好的:P。

Your instinct to use environment variables is correct. However, there is some danger of running unit tests with the wrong db. Also, you may not want to connect_db with every request and everywhere you want to use db. You can use a config directory and environment variables which you set explicitly. This is the best I've come up with so far.

run.py
shell.py

config/__init__.py
config/test.py
config/postgres.py
...

main/__init__.py
main/someapp/__init__.py
main/someapp/models.py

...
main/tests/__init__.py
main/tests/testutils.py

so, the config files may be:

# config/test.py
SQLALCHEMY_DATABASE_URI = "sqlite://"

and

# config/postgres.py
SQLALCHEMY_DATABASE_URI = 'postgresql://user:pw@localhost/somedb'

So, I can explicitly set the db in my base TestCase:

import os
from flask.ext.testing import TestCase

os.environ["DIAG_CONFIG_MODULE"] = "config.test"
from main import app, db


class SQLAlchemyTest(TestCase):

    def create_app(self):
        return app

    def setUp(self):
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()

Then, the main/__init__.py, for me:

import os

from flask import Flask, render_template, g
from flask.ext.sqlalchemy import SQLAlchemy

# by default, let's use a DB we don't care about
# but, we can override if we want
config_obj = os.environ.get("DIAG_CONFIG_MODULE", "config.test")
app = Flask(__name__)
app.config.from_object(config_obj)
db = SQLAlchemy(app)

@app.before_request
def before_request():
    g.db = db
    g.app = app

# ...
@app.route('/', methods=['GET'])
def get():
    return render_template('home.html')
# ...    
from main.someapp.api import mod as someappmod
app.register_blueprint(someappmod)

Then, in the other files, where I know what config I want to run, potentially:

# run.py
import os
os.environ["DIAG_CONFIG_MODULE"] = "config.postgres"
from main import app
app.run(debug=True)

and

# shell.py
import os
os.environ["DIAG_CONFIG_MODULE"] = "config.postgres"

from main import app, db
from main.symdiag.models import *
from main.auth.models import *
print sorted(k for k in locals().keys() if not k.startswith("_"))
import IPython
IPython.embed()

Maybe .. best so far :P.

月下客 2024-10-24 23:12:05

您不希望在导入时连接到数据库。继续在导入时配置您的应用程序,因为您始终可以在尝试测试或运行应用程序之前调整测试中的配置。在下面的示例中,您的数据库连接将位于一些使用应用程序配置的函数后面,因此在单元测试中,您实际上可以更改数据库连接以指向不同的文件,然后继续并在您的设置中显式连接。

假设您有一个包含 myapp.py 的 myapp 包,如下所示:

# myapp/myapp.py
from __future__ import with_statement
from sqlite3 import dbapi2 as sqlite3
from contextlib import closing
from flask import Flask, request, session, g, redirect, url_for, abort, \
     render_template, flash

# configuration
DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

# create our little application :)
app = Flask(__name__)
app.config.from_object(__name__)
app.config.from_envvar('MYAPP_SETTINGS', silent=True)

def connect_db():
    """Returns a new connection to the database."""
    return sqlite3.connect(app.config['DATABASE'])


def init_db():
    """Creates the database tables."""
    with closing(connect_db()) as db:
        with app.open_resource('schema.sql') as f:
            db.cursor().executescript(f.read())
        db.commit()


@app.before_request
def before_request():
    """Make sure we are connected to the database each request."""
    g.db = connect_db()


@app.after_request
def after_request(response):
    """Closes the database again at the end of the request."""
    g.db.close()
    return response

@app.route('/')
def show_entries():
    cur = g.db.execute('select title, text from entries order by id desc')
    entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
    return render_template('show_entries.html', entries=entries)

if __name__=="__main__":
    app.run()

您的测试文件 myapp/test_myapp.py 将如下所示:

import os
import myapp
import unittest
import tempfile

class MyappTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, myapp.app.config['DATABASE'] = tempfile.mkstemp()
        self.app = myapp.app.test_client()
        myapp.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(myapp.app.config['DATABASE'])

    def test_empty_db(self):
        rv = self.app.get('/')
        assert 'No entries here so far' in rv.data

当然,如果您想使用 SQLAlchemy,则必须适当更新 connect_db 和 init_db 函数,但是希望你能明白。

You won't want to make connecting to the db happen at import time. Go ahead and configure your app at import time because you can always tweak the configuration in your tests before attempting to test or run your app. In the example below you'll have your db connection behind some functions that use the application config so in a unittest you can actually change the db connection to point to a different file and then go ahead and connect explicitly in your setup.

Say you have a myapp package containing myapp.py which looks like:

# myapp/myapp.py
from __future__ import with_statement
from sqlite3 import dbapi2 as sqlite3
from contextlib import closing
from flask import Flask, request, session, g, redirect, url_for, abort, \
     render_template, flash

# configuration
DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

# create our little application :)
app = Flask(__name__)
app.config.from_object(__name__)
app.config.from_envvar('MYAPP_SETTINGS', silent=True)

def connect_db():
    """Returns a new connection to the database."""
    return sqlite3.connect(app.config['DATABASE'])


def init_db():
    """Creates the database tables."""
    with closing(connect_db()) as db:
        with app.open_resource('schema.sql') as f:
            db.cursor().executescript(f.read())
        db.commit()


@app.before_request
def before_request():
    """Make sure we are connected to the database each request."""
    g.db = connect_db()


@app.after_request
def after_request(response):
    """Closes the database again at the end of the request."""
    g.db.close()
    return response

@app.route('/')
def show_entries():
    cur = g.db.execute('select title, text from entries order by id desc')
    entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
    return render_template('show_entries.html', entries=entries)

if __name__=="__main__":
    app.run()

Your test file myapp/test_myapp.py will look like this:

import os
import myapp
import unittest
import tempfile

class MyappTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, myapp.app.config['DATABASE'] = tempfile.mkstemp()
        self.app = myapp.app.test_client()
        myapp.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(myapp.app.config['DATABASE'])

    def test_empty_db(self):
        rv = self.app.get('/')
        assert 'No entries here so far' in rv.data

Of course if you'd like to use SQLAlchemy you'll have to update the connect_db and init_db functions appropriately but hopefully you get the idea.

帝王念 2024-10-24 23:12:05

首先,您不是直接在脚本中实例化 Flask 应用程序,而是使用应用程序工厂。这意味着您创建一个以配置文件作为参数的函数,并返回实例化的应用程序对象。然后,您创建不带参数的全局 SQLAlchemy 对象,并在创建应用程序时配置它,如此处所述

db = SQLAlchemy()

def create_app(configfile):
    app = Flask(__name__)

    app.config.from_pyfile(config, silent=True)
    db.init_app(app)

    # create routes, etc.

    return app

要运行该应用程序,您只需执行以下操作:

app = create_app('config.py')
app.run()

要运行单元测试,您可以执行以下操作:

class Test(TestCase):
    def setUp(self):
        # init test database, etc.
        app = create_app('test_config.py')
        self.app = app.test_client()
    def tearDown(self):
        # delete test database, etc.

在我的例子中,我直接使用 SQLAlchemy 和scoped_session 而不是 Flask-SQLAlchemy。
我也做了同样的事情,但是使用了Lazy SQLAlchemy 设置

First, instead of instantiating Flask app directly in your script, you use an application factory. It means you create a function that takes your config file as parameter, and return the instantiated app object. Then, you create the global SQLAlchemy object without parameter, and you configure it when creating the app, as explained here.

db = SQLAlchemy()

def create_app(configfile):
    app = Flask(__name__)

    app.config.from_pyfile(config, silent=True)
    db.init_app(app)

    # create routes, etc.

    return app

To run the app, you simply do something like:

app = create_app('config.py')
app.run()

To run unittests, you can do something like:

class Test(TestCase):
    def setUp(self):
        # init test database, etc.
        app = create_app('test_config.py')
        self.app = app.test_client()
    def tearDown(self):
        # delete test database, etc.

In my case, I'm using SQLAlchemy directly with scoped_session instead of Flask-SQLAlchemy.
I did the same, but with Lazy SQLAlchemy setup.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文