首页 文章

在Blueprint模型中使用Flask-SQLAlchemy而不参考应用程序[关闭]

提问于
浏览
59

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

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

  • 可以执行switcharoo,并让Blueprint创建 db 实例,然后应用程序将其与蓝图的其余部分一起导入 . 但是,任何其他希望创建模型的蓝图都需要从 that 蓝图而不是应用程序导入 .

我的问题是这样的:

  • 有没有办法让蓝图定义模型而不了解他们以后使用的应用程序 - 并且有几个蓝图汇集在一起?通过这个,我的意思是必须从您的蓝图导入应用程序模块/包 .

  • 从一开始我错了吗?蓝图是不是要独立于应用程序并且是可再发行的(Django应用程序)?

  • 如果没有,那么你应该使用什么模式来创建类似的东西?烧瓶扩展?你应该不这样做 - 并且可能将所有模型/模式集中在Ruby on Rails上?

编辑:我现在一直在考虑这个问题,这可能与SQLAlchemy有关,而不是Flask,因为在声明模型时你必须有declarative_base() . 无论如何,这必须来自某个地方!也许最好的解决方案是将项目的模式定义在一个地方并将其展开,就像Ruby on Rails一样 . 声明式SQLAlchemy类定义实际上更像是schema.rb而不是Django的models.py . 我想这也可以更容易地使用迁移(来自alembic或sqlalchemy-migrate) .


我被要求提供一个例子,所以让我们做一些简单的事情:说我有一个蓝图描述"flatpages" - 简单,"static"内容存储在数据库中 . 它使用一个只有短名称(用于URL), Headers 和正文的表 . 这是 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

这个问题与以下内容有关:

和其他各种各样,但所有回复似乎都依赖于导入app的 db 实例,或者反过来 . "Large app how to"维基页面也使用"import your app in your blueprint"模式 .

*由于官方文档显示了如何在蓝图中创建路线,视图,模板和资产而不关心它在哪个应用程序中,我认为蓝图通常应该可以在应用程序之间重复使用 . 然而,如果没有独立的模型,这种模块化似乎没有用 .

由于蓝图可以不止一次地连接到应用程序,因此在蓝图中使用模型可能只是错误的方法?

3 回答

  • 33

    我认为最真实的答案是模块化蓝图不应直接关注数据访问,而应依赖于提供兼容实现的应用程序 .

    所以给出你的示例蓝图 .

    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.Model的直接继承而不仅仅使用sqlalchemy中的vanilla declarative_base,上面的内容可以变得更加清晰,但这应该代表它的要点 .

  • 0

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

    我们的想法是在蓝图中创建一个单独的 db 对象( db = SQLAlchemy() ),然后从创建根应用程序的地方调用 init_app()create_all() 方法 .

    下面是一些示例代码,用于显示项目的结构:应用程序名为 jobs ,蓝图名为 status ,它存储在blueprints文件夹中 .

    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 worker测试了它,它可以工作 . 我在这里问了一个关于解决方案稳健性的单独问题:Create one SQLAlchemy instance per blueprint and call create_all multiple times

  • 2

    您问“蓝图是不是要独立于应用程序并且是可再发行的(Django应用程序)?”

    答案是肯定的 . 蓝图与Django App不同 .

    如果你想要使用不同的应用程序/配置,那么你需要使用"Application Dispatching"而不是蓝图 . 阅读本文[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层进行管理 . “

相关问题