在 Blueprint 模型中使用 Flask-SQLAlchemy 而无需参考应用程序

我正在尝试使用 Blueprint 在 Flask 中创建一个“模块化应用程序”。

然而,在创建模型时,我遇到了这样的问题: 为了获得 Flask-SQLAlchemy 提供的 db对象,我必须引用这个应用程序。我希望能够在多个应用程序中使用一些蓝图(类似于 Django 应用程序的使用方式) ,所以这不是一个好的解决方案。*

  • 可以进行一次切换,让 Blueprint 创建 db实例,然后应用程序将其与蓝图的其余部分一起导入。但是,任何其他蓝图希望创建模型需要从 那个蓝图导入,而不是应用程序。

我的问题如下:

  • 有没有一种方法可以让 Blueprint 定义模型,而无需知道它们将在以后的应用程序中使用——并将多个 Blueprint 放在一起?这里的意思是必须从 Blueprint 中导入应用程序模块/包。
  • 我从一开始就错了吗?Blueprint 难道不应该独立于应用程序并且可以重新发布吗(就像 Django 应用程序一样) ?
    • 如果没有,那么您使用什么样的模式 应该来创建类似的东西?酒瓶接头?您是否应该简单地不这样做——或许应该将所有模型/模式集中到 Ruby on Rails 中?

编辑 : 我自己一直在思考这个问题,这可能与 SQLAlchemy 比 Flask 更相关,因为在声明模型时必须使用 declarative_base()。不管怎样,那是肯定是从某个地方来的!

也许最好的解决方案是将项目的模式定义在一个地方,并像 RubyonRails 那样分散开来。声明性 SQLAlchemy 类定义实际上更像 schema.rb,而不是 Django 的 models.py。我想这也会使迁移(从 Alembic移民)更容易使用。


我被要求提供一个示例,所以让我们做一些简单的事情: 假设我有一个描述“平面页面”的蓝图——存储在数据库中的简单的“静态”内容。它使用一个只有简称(URL)、标题和主体的表。这里是 simple_pages/__init__.py:

from flask import Blueprint, render_template
from .models import Page


flat_pages = Blueprint('flat_pages', __name__, template_folder='templates')


@flat_pages.route('/<page>')
def show(page):
page_object = Page.query.filter_by(name=page).first()
return render_template('pages/{}.html'.format(page), page=page_object)

然后,最好让这个蓝图定义它自己的模型(这在 simple_page/models.py中) :

# TODO Somehow get ahold of a `db` instance without referencing the app
# I might get used in!


class Page(db.Model):
name = db.Column(db.String(255), primary_key=True)
title = db.Column(db.String(255))
content = db.Column(db.String(255))


def __init__(self, name, title, content):
self.name = name
self.title = title
self.content = content

这个问题涉及到:

和其他各种,但所有的答复似乎都依赖于导入应用程序的 db实例,或者做相反的事情。“大型应用程序如何” wiki 页面也使用了“在你的蓝图中导入你的应用程序”模式。

* 由于官方文档显示了如何在 Blueprint 中创建路由、视图、模板和资产,而不关心它“在”哪个应用程序中,所以我假设 Blueprint 通常应该可以跨应用程序重用。然而,如果没有独立的模型,这种模块化似乎就没有 那个的用处。

既然 Blueprint 可以不止一次地连接到一个应用程序中,那么在 Blueprint 中使用模型可能就是一种错误的方法?

33837 次浏览

你问“ Blueprint 难道不应该独立于应用程序并且可以重新发布(就像 Django 应用程序那样)吗?”

答案是肯定的,蓝图和 Django App 不一样。

如果您想使用不同的应用程序/配置,那么您需要使用“应用程序调度”而不是蓝图 [1] : http://flask.pocoo.org/docs/patterns/appdispatch/#app-dispatch[1]

此外,这里的链接[1] http://flask.pocoo.org/docs/blueprints/#the-concept-of-blueprints[1]

它清楚地说,我引用“ Flask 中的蓝图不是一个可插拔的应用程序,因为它实际上不是一个应用程序——它是一组操作,可以在一个应用程序上注册,甚至多次。为什么不使用多个应用程序对象?您可以这样做(请参阅应用程序调度) ,但是您的应用程序将有单独的配置,并将在 WSGI 层进行管理。”

我相信最正确的答案是,模块化蓝图不应该直接关注数据访问,而应该依赖于提供兼容实现的应用程序。

因此,给出了你的示例蓝图。

from flask import current_app, Blueprint, render_template


flat_pages = Blueprint('flat_pages', __name__, template_folder='templates')


@flat_pages.record
def record(state):
db = state.app.config.get("flat_pages.db")


if db is None:
raise Exception("This blueprint expects you to provide "
"database access through flat_pages.db")


@flat_pages.route('/<page>')
def show(page):
db = current_app.config["flat_pages.db"]
page_object = db.find_page_by_name(page)
return render_template('pages/{}.html'.format(page), page=page_object)

因此,没有什么可以阻止您提供缺省实现。

def setup_default_flat_pages_db(db):
class Page(db.Model):
name = db.Column(db.String(255), primary_key=True)
title = db.Column(db.String(255))
content = db.Column(db.String(255))


def __init__(self, name, title, content):
self.name = name
self.title = title
self.content = content


class FlatPagesDBO(object):
def find_page_by_name(self, name):
return Page.query.filter_by(name=name).first()


return FlatPagesDBO()

在你的配置中。

app.config["flat_pages.db"] = setup_default_flat_pages_db(db)

通过不依赖来自 db 的直接继承,上面的内容可以变得更简洁。模型,而只是使用来自 sqltancy 的香草声明 _ base,但是这应该代表它的要点。

我有类似的需求,使蓝图完全模块化,没有参考的应用程序。我想出了一个可能干净的解决方案,但我不确定它是否正确,它的局限性是什么。

其思想是在蓝图中创建一个单独的 db对象(db = SQLAlchemy()) ,并从中调用创建根应用程序的 init_app()create_all()方法。

下面是一些示例代码,展示了项目是如何构建的: 应用程序名为 jobs,蓝图名为 status,它存储在蓝图文件夹中。

blueprints.status.models.py

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()  # <--- The db object belonging to the blueprint


class Status(db.Model):
__tablename__ = 'status'
id = db.Column(db.Integer, primary_key=True)
job_id = db.Column(db.Integer)
status = db.Column(db.String(120))

models.py

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()  # <--- The db object belonging to the root app


class Job(db.Model):
__tablename__ = 'job'
id = db.Column(db.Integer, primary_key=True)
state = db.Column(db.String(120)

factory.py

from .blueprints.status.models import db as status_db  # blueprint db
from .blueprints.status.routes import status_handler   # blueprint handler
from .models import db as root_db                      # root db
from flask import Flask


def create_app():
app = Flask(__name__)


# Create database resources.
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////path/to/app.db'
root_db.init_app(app)
status_db.init_app(app)     # <--- Init blueprint db object.
with app.app_context():
root_db.create_all()
status_db.create_all()  # <--- Create blueprint db.


# Register blueprint routes.
app.register_blueprint(status_handler, url_prefix="/status")


return app

我用 gunicorngevent工作人员测试了它,它工作正常。我另外问了一个关于解决方案稳健性的问题: 每个蓝图创建一个 SQLAlchemy 实例,并多次调用 Create _ all