12

さまざまな Python モジュールに中央クラス モデルを使用します。このモデルは、SQLAlchemy を使用して定義されます。クラスはすべて declarative_base から継承します。

たとえば、モデル定義は次のようになります。

Base = declarative_base()

class Post(Base):
    __tablename__ = 'Posts'
    id = Column(INT, primary_key=True, autoincrement=True)
    body = Column(TEXT)
    timestamp = Column(TIMESTAMP)
    user_id = Column(INT, ForeignKey('Users.uid'))

この同じモデルを使用するフラスコ Web アプリケーションを構築しています。私たちは、flask-sqlalchemy が、そのモデルで使用されるすべてのクラスがセッションのアクティブなインスタンスを渡すことによって定義されていることを期待するように設計されているように見えるというトリッキーな問題を発見しました。以下は、「適切な」flask-sqalchemy クラス モデル定義の例です。

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    email = db.Column(db.String(120), unique=True)

上記のフラスコ sqlalchemy の例では、既にインスタンス化された sql セッションが必要であることに注意してください。SqlAlchemy モデルをフラスコに統合する方法について完全に途方に暮れているため、これは私たちをぞっとさせました。特にフラスコ セキュリティ スイートを使用したいと考えています。

この問題は SO で以前に提起されました。例: 既存の sqlalchemy モデルでフラスコ sqlalchemy を使用するには?

私たちの要件は、そこで応答を受け入れた人の要件とは異なります。応答は、User.query を使用できなくなることを指摘していますが、これはまさに保持しなければならないことの 1 つです。

素敵で洗練された中心的なクラス モデルの定義を放棄して、flask-sqlalchemy が必要と思われるものを採用することは現実的ではありません。モデルを SQLAlchemy() オブジェクトに関連付ける方法はありますか? クラスで .query() メソッドを取得するためのボーナス ポイントは、flask-security で必要と思われるものです。

4

1 に答える 1

19

解決:

現在のところ、これを行う最善の方法は次のとおりです。

sqlalchemy を実装またはインポートするbase

from sqlalchemy.ext.declarative import declarative_base

base = declarative_base()


class Base(base):

    __abstract__ = True
    uid = Column(Integer, primary_key=True, autoincrement=True)

外部ベースを登録します。

from flask_sqlalchemy import SQLAlchemy
from model.base import Base

app = Flask(__name__)
db = SQLAlchemy(app, model_class=Base)

後世のためにアーカイブ:

これに対する答えを探すのに多くの時間を費やしました。これは、私が最初に質問したときよりもはるかに簡単になりましたが、まだ単純ではありません。

セキュリティを自分で行うことに決めた人には、flask を使用するが、flask-security のような不要な依存関係を採用しない一般的な設計パターンの次の優れた解説をお勧めします: https://exploreflask.com/users.html

更新: 興味のある方のために、これに関連してしばらくの間パッチが作成されています。現時点ではまだリリースされていませんが、ここで進捗状況を確認できます: https://github.com/mitsuhiko/flask-sqlalchemy/pull/250#issuecomment-77504337

更新: 上記のパッチからコードを取得し、SQLAlchemy オブジェクトのローカル オーバーライドを作成して、外部ベースを登録できるようにしました。金融庁が正式に追加するまでは、これが最善の選択肢だと思います。興味のある人のために、そのクラスのコードを次に示します。Flask-SqlAlchemy 2.2 で動作テスト済み

register_external_base でのパッチ適用:

import flask_sqlalchemy
'''Created by Isaac Martin 2017. Licensed insofar as it can be according to the standard terms of the MIT license: https://en.wikipedia.org/wiki/MIT_License. The author accepts no liability for consequences resulting from the use of this software. '''
class SQLAlchemy(flask_sqlalchemy.SQLAlchemy):
    def __init__(self, app=None, use_native_unicode=True, session_options=None,
                 metadata=None, query_class=flask_sqlalchemy.BaseQuery, model_class=flask_sqlalchemy.Model):

        self.use_native_unicode = use_native_unicode
        self.Query = query_class
        self.session = self.create_scoped_session(session_options)
        self.Model = self.make_declarative_base(model_class, metadata)
        self._engine_lock = flask_sqlalchemy.Lock()
        self.app = app
        flask_sqlalchemy._include_sqlalchemy(self, query_class)
        self.external_bases = []

        if app is not None:
            self.init_app(app)

    def get_tables_for_bind(self, bind=None):
        """Returns a list of all tables relevant for a bind."""
        result = []
        for Base in self.bases:
            for table in flask_sqlalchemy.itervalues(Base.metadata.tables):
                if table.info.get('bind_key') == bind:
                    result.append(table)

        return result

    def get_binds(self, app=None):
        """Returns a dictionary with a table->engine mapping.
        This is suitable for use of sessionmaker(binds=db.get_binds(app)).
        """
        app = self.get_app(app)
        binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
        retval = {}
        for bind in binds:
            engine = self.get_engine(app, bind)
            tables = self.get_tables_for_bind(bind)
            retval.update(dict((table, engine) for table in tables))
        return retval

    @property
    def bases(self):
        return [self.Model] + self.external_bases

    def register_base(self, Base):
        """Register an external raw SQLAlchemy declarative base.
        Allows usage of the base with our session management and
        adds convenience query property using self.Query by default."""

        self.external_bases.append(Base)
        for c in Base._decl_class_registry.values():
            if isinstance(c, type):
                if not hasattr(c, 'query') and not hasattr(c, 'query_class'):
                    c.query_class = self.Query
                if not hasattr(c, 'query'):
                    c.query = flask_sqlalchemy._QueryProperty(self)

                    # for name in dir(c):
                    #     attr = getattr(c, name)
                    #     if type(attr) == orm.attributes.InstrumentedAttribute:
                    #         if hasattr(attr.prop, 'query_class'):
                    #             attr.prop.query_class = self.Query

                    # if hasattr(c , 'rel_dynamic'):
                    #     c.rel_dynamic.prop.query_class = self.Query

次のように使用するには:

app = Flask(__name__)
db = SQLAlchemy(app)
db.register_base(base)
于 2015-03-03T19:10:56.727 に答える