web-dev-qa-db-ja.com

アプリを参照せずにブループリントモデルでFlask-SQLAlchemyを使用する

ブループリントを使用してFlaskで「モジュラーアプリケーション」を作成しようとしています。

ただし、モデルを作成するとき、Flask-SQLAlchemyが提供するdb-オブジェクトを取得するためにアプリを参照する必要があるという問題に直面しています。複数のアプリでいくつかの設計図を使用できるようにしたい(Djangoアプリの使用方法と同様))ので、これは良い解決策ではありません。*

  • Switcharooを実行して、ブループリントにdbインスタンスを作成させ、アプリが残りのブループリントとともにインポートすることが可能です。しかし、その後、モデルを作成したい他のブループリントは、アプリではなくthatブループリントからインポートする必要があります。

したがって、私の質問は次のとおりです。

  • 後で使用されているアプリを意識せずに、ブループリントにモデルを定義させる方法はありますか?また、いくつかのブループリントが一緒になりますか?これにより、アプリモジュール/パッケージをブループリントからインポートする必要があります。
  • 私は最初から間違っていますか?ブループリントは、アプリから独立して再配布できるものではありませんか(àla Django apps)?
    • そうでない場合、どのようなパターンshouldを使用してそのようなものを作成しますか? Flask extensions?単純にやらないほうがいいでしょう。そして、おそらくすべてのモデル/スキーマを一元化してくださいRuby on Rails?

編集:私はこれについて自分自身で考えていましたが、これはFlaskよりもSQLAlchemyに関連しているかもしれません。なぜなら、declarative_base()そして、とにかく、that'sはどこかから来るようになりました!

おそらく最良の解決策は、プロジェクトのスキーマを1か所で定義して、Ruby on Railsが行います。宣言SQLAlchemyクラス定義は本当にもっとDjangoのmodels.pyよりschema.rbのようになります。これにより、移行の使用が容易になると思います( alembic または sqlalchemy-migrate から)。


例の提供を求められたので、簡単なことをしましょう。「フラットページ」を説明する青写真があり、データベースに保存されたシンプルで「静的な」コンテンツがあるとします。短い名前(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インスタンスのインポート、またはその逆に依存しているようです。 "Large app how to" wikiページでも、「アプリを設計図にインポートする」パターンを使用します。

*公式ドキュメントでは、どのアプリが「イン」されているかを気にせずにルート、ビュー、テンプレート、およびアセットをブループリントに作成する方法を示しているため、ブループリントは一般にアプリ間で再利用可能であると想定しました。ただし、このモジュール性は、独立したモデルがなくてもthatのようには見えません。

ブループリントはアプリに複数回フックできるため、ブループリントにモデルを配置するのは単に間違ったアプローチかもしれません。

63
vicvicvic

本当の答えは、モジュラーブループリントはデータアクセスに直接関係するのではなく、互換性のある実装を提供するアプリケーションに依存するということだと思います。

設計図の例を考えてみましょう。

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を使用するだけでよりクリーンになりますが、これはその要旨を表す必要があります。

35
udoprog

ブループリントを完全にモジュール化し、アプリを参照しないという同様のニーズがあります。おそらくクリーンなソリューションを思いつきましたが、それがどれほど正しいか、そしてその制限は何なのか分かりません。

アイデアは、ブループリント内に別個の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):
    __table= '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):
    __table= '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

gunicorn workerでgeventでテストしましたが、動作します。ここでソリューションの堅牢性について別の質問をしました: ブループリントごとに1つのSQLAlchemyインスタンスを作成し、create_allを複数回呼び出します

2
nitred

「ブループリントはアプリから独立して再配布できるものではありませんか(àla Django apps)?」

答えはイエスです。ブループリントは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レイヤーで管理されます。」

0
codegeek