Pythonic方法使用SQLAlchemy正确地将Model与应用程序分开

时间:2013-09-25 14:52:27

标签: python flask flask-sqlalchemy

我很难让我的应用程序运行。每当我尝试在包中分离模块时,Flask-SQLAlchemy扩展会创建一个空数据库。为了更好地解释我正在做的事情,让我展示一下我的项目是如何构建的:

Project
|
|-- Model
|   |-- __init__.py
|   |-- User.py
|
|-- Server
|   |-- __init__.py
|
|-- API
|   |-- __init__.py

这个想法很简单:我想为我的模型创建一个包,因为我不喜欢在单个包中传播代码,而是将“子”项目(如API)分开,因为将来我会使用蓝图以更好地隔离子应用程序。

代码非常简单:

首先,Model.__init__.py

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

请注意,我创建它只是为了使用整个包中的单个SQLAlchemy()对象。不,我们去Model.User

from Model import db

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

再次注意我用过的模型导入数据库允许使用相同的数据库对象。

最后,Server.__init__.py是这样的:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import Model, API
db = Model.db


def main():
    app = Flask("__main__")
    db = SQLAlchemy(app)
    db.create_all()
    API.SetAPIHookers(app)
    app.run(host="0.0.0.0", port=5000, debug=True)

if __name__ == "__main__":
    main()

从我的角度来看,db = SQLAlchemy(app)允许我传递我的app对象而不创建循环引用。

问题在于每当我运行此代码时,sqlite数据库文件都是空的。这让我觉得Python可能不会像我想象的那样导入东西。所以我通过删除导入模型并直接在服务器内创建用户来测试我的理论......并且瞧,它有效!

现在我的问题是:是否有一种“pythonic”方式可以正确地分离我想要的模块,还是应该将所有内容放在同一个包中?

2 个答案:

答案 0 :(得分:22)

现在,您已经使用与“Application Factory”模式相同的粗略设置来设置应用程序(Flask文档也称之为)。这是一个Flask的想法,而不是Python的想法。它有一些优点,但它也意味着你需要做一些事情,比如使用init_app方法而不是SQLAlchemy构造函数初始化SQLAlchemy对象。这样做没有“错误”,但这意味着您需要在application context内运行create_all()之类的方法,如果您尝试在Larger Application Patterns中运行它,则目前不会这样。 {1}}方法。

有几种方法可以解决这个问题,但是由你来决定你想要哪一个(没有正确答案):

不要使用Application Factory模式

这样,您就不会在函数中创建应用程序。相反,你把它放在某个地方(比如main())。您的project/__init__.py文件可以导入project/__init__.py包,而models包可以从models导入app。这是一个循环引用,但只要project包中的app对象在project尝试从model导入app之前创建就可以了。 。有关可以将包拆分为多个包的示例,请参阅as described in the documentation for Flask-SQLAlchemy上的Flask文档,但仍然可以通过使用循环引用使这些其他包能够使用package对象。文档甚至说:

  

每个Python程序员都讨厌它们,但我们只是添加了一些:   循环进口。 [...]请注意,这是一个   总的来说不好的想法,但这里实际上很好。

如果您这样做,那么您可以更改app文件以构建Models/__init__.py对象,并在构造函数中引用该应用程序。这样,您就可以使用SQLAlchemy对象的create_all()drop_all()方法,before_first_request()

现在保留你的拥有方式,但是建立在request_context()

如果继续使用现有功能(在函数中创建应用程序),则需要在SQLAlchemy包中构建SQLAlchemy对象,而不使用Models对象作为构造函数的一部分(就像你所做的那样)。在主方法中,更改...

app

...到......

db = SQLAlchemy(app)

然后,您需要将db.init_app(app) 方法移动到应用程序上下文中的函数中。在项目早期对此进行此操作的常用方法是使用{{3}}装饰器....

create_all()

在“Flask”处理第一个请求之前运行“initialize_database”方法。您也可以使用app = Flask(...) @app.before_first_request def initialize_database(): db.create_all() 方法随时执行此操作:

app_context()

要意识到如果您要继续使用Application Factory模式,您应该真正了解应用程序上下文的工作原理;一开始可能会让人感到困惑,但是必须要意识到“应用程序没有在数据库实例上注册而没有应用程序绑定到当前上下文”这样的错误。

答案 1 :(得分:5)

你的问题就在这一行:

db = SQLAlchemy(app)

它应该是这样的:

db.init_app(app)

通过再次运行SQLAlchemy应用程序,您可以将db重新分配给新创建的db obj。

请尽量不要离开应用程序工厂设置。它消除了导入时间的副作用,是一件好事。实际上,您可能希望在工厂中导入db,因为导入Base的子类(在本例中为db.model)具有自己的副作用(不是问题)。

__init__.py中初始化您的应用意味着当您从包中导入任何内容以供使用时,即使您不需要,也可以最终引导您的应用。< / p>