在不参考应用程序的情况下在蓝图模型中使用Flask-SQLAlchemy


68

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

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

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

因此,我的问题是:

  • 有没有一种方法可以让蓝图定义模型,而又不知道它们以后将使用的应用程序-并且有多个蓝图组合在一起?通过这种方式,我的意思是必须从您的蓝图导入应用程序模块/软件包。
  • 我从一开始就错了吗?蓝图是否不是要独立于应用程序并可以重新分发(例如Django应用程序)?
    • 如果没有,那么您应该使用什么模式来创建类似的东西?烧瓶扩展?您是否应该简单地做到这一点-也许集中所有模型/方案以及Ruby on Rails?

编辑:我现在一直在考虑这个问题,这可能与SQLAlchemy而不是Flask有关,因为declarative_base()在声明模型时必须具有。而得从什么地方来的,反正!

也许最好的解决方案是像Ruby on Rails一样,将项目的模式定义在一个位置并进行分散。声明性SQLAlchemy类定义实际上更像是schema.rb,而不是Django的models.py。我想这也将使使用迁移(从Alembicsqlalchemy-migrate)更容易。


我被要求提供一个示例,所以让我们做一些简单的事情:假设我有一个描述“ flatpages”的蓝图,“ flatpages”是存储在数据库中的简单,“静态”内容。它使用仅带有短名称(用于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页面也使用“导入您的应用程序在你的蓝图”的格局。

*由于官方文档显示了如何在蓝图中创建路线,视图,模板和资产,而无需关心它在“哪个”应用中,因此我认为,蓝图通常应可在所有应用之间重用。然而,这种模块化似乎并不认为而不让自主车型有用。

由于可以将蓝图多次挂接到应用程序中,因此在蓝图中建立模型可能只是错误的方法?


您作为可插入应用程序创建的蓝图是否需要定义它们使用的模型?还是可以从应用程序中简单地为其提供模型?如果是前者,可以举一个您要创建的,需要定义自己模型的可重用蓝图的示例吗?
肖恩·维埃拉

2
我按照您的要求提供了一个简单的示例。在这种情况下,很难说“需要”:如果这是根本错误的处理方法,则不必“定义”自己的模型,但我的印象是,您应该“这样做”。但是,我可以理解Flask是否像RoR那样更好地使用,在您的应用中定义了架构并“提供”给各种插件的RoR中。我想在那种情况下,您会使用配置来提供吗?
vicvicvic 2012年

为什么这种解决方案在您的情况下不起作用?它不引用模型或蓝图模块中的应用程序。
陶鹏

它仍然依赖于共享的名称空间,shared.apps这使得很难在应用程序之间使用相同的蓝图(在这样的名称空间上可能没有任何协议)
vicvicvic 2012年

Answers:


38

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

因此,给出您的示例蓝图。

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,但这应该代表其要点。


很有趣!我不知道这是否是您“应该做的”方式,但是由于似乎没有解决该问题的真正方法,因此我会接受您的回答。
vicvicvic

此处的答案:stackoverflow.com/a/9695045/6421681应该与这个一起考虑。这个其他答案不需要导入app,但是确实需要导入db; 但是,答案中有一个聪明的解决方法。
Yolo Voe

3

我有使Blueprints完全模块化且没有对该应用程序的引用的类似需求。我想出了一个可能很干净的解决方案,但不确定它的正确性和局限性。

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

以下是一些示例代码,用于显示项目的结构:调用应用程序并调用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工人进行了测试,并且可以正常工作。我在这里问了有关解决方案稳定性的一个单独问题: 每个蓝图创建一个SQLAlchemy实例,并多次调用create_all


既然您的单独问题已删除,您现在还有其他要补充的内容吗?
melutovich

0

您问:“蓝图不是要独立于应用程序而不能重新分发(例如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层进行管理。”


3
是的,我很欣赏它们与Django应用并不完全一样。但是,对我来说,这种描述并不是说它们既不独立也不可再发行。在我看来,“蓝图”通常旨在在多个应用程序上注册。Django应用程序也不像Flask应用程序一样-它没有自己的配置或WSGI分隔。
vicvicvic 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.