web-dev-qa-db-ja.com

flaskと他のアプリの間でsqlalchemyモデルを共有する

私はオンラインで実行しているFlaskアプリケーションを実行しています。これは、オンラインとミゲルグリンバーグの「Flask Web開発」の本で見つけたベストプラクティスの組み合わせに従ってセットアップされています。

ここで、2番目のPythonアプリケーションが必要です。これはWebアプリケーションではなく、Flaskアプリケーションと同じモデルにアクセスする必要があります。 -同じモデルのコースを使用して、両方のアプリが共有コードの恩恵を受けることができるようにします。

Flask-sqlalchemyの拡張機能(Flaskアプリケーションのみの場合に以前使用したもの)への依存関係を削除しました。そして、説明した SQLalchemy宣言型拡張機能で置き換えましたhere 、これは少し単純です( Flask-SQLalchemyは、標準のSQLAlchemy にいくつかの特定のものを追加します)

例に沿って、ルートにdatabase.pyファイルを作成しました。私たちの場合、宣言型拡張の例とは2つの点が異なります。すべてのモデルがdb_sessionではなくdb.sessionを使用するため、エンジンとセッションをクラスに配置し、設定値を含む辞書をinit()これにより、このdatabase.pyをFlaskと他のアプリケーションの両方から再利用できます。別の構成です。次のようになります。

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base


class Database(object):

    def __init__(self, cfg):
        self.engine = create_engine(cfg['SQLALCHEMY_DATABASE_URI'], convert_unicode=True)
        self.session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=self.engine))

    class Model(object):
        pass

Base = declarative_base()

それでは、実際の問題に行きましょう。 Flaskは、構成オプションを含む辞書のようなオブジェクトを作成し、それらをプロパティとしてapp-instanceに追加します。 instanceフォルダーからそれらをロードします 、サイトのルートと環境変数からの config.py 。Flaskから構成辞書を渡す必要があるため、Flask最初に構成をロードしてアセンブルし、その後データベースを初期化して、(構成済みの)dbオブジェクトをアプリファイルのルートに配置します。ただし、 アプリケーションファクトリパターン なので、さまざまな状況(テスト、本番、開発)でさまざまな構成を使用できます。

つまり、app/__init__.pyは次のようになります(簡略化)。

from flask import Flask
from database import Database
from flask.ext.mail import Mail
from flask_bcrypt import Bcrypt
from config import config

mail = Mail()
bcrypt = Bcrypt()


def create_app(config_name):

    app = Flask(__name__, instance_relative_config=True)

    if not config_name:
        config_name = 'default'
    app.config.from_object(config[config_name])
    app.config.from_pyfile('config.py')
    config[config_name].init_app(app)

    db = Database(app.config)

    mail.init_app(app)
    bcrypt.init_app(app)

    @app.teardown_appcontext
    def shutdown_session(exception=None):
        db.session.remove()

    from main import main as main_blueprint
    app.register_blueprint(main_blueprint)

    return app

しかし、db(モデルが..からインポートするもの)は、create_app()関数内にある必要があります。これは、ここでFlaskが構成をロードするためです。dbオブジェクトをcreate_app()関数。モデルからインポート可能ですが、構成されていません!

サンプルモデルは次のようになり、ご覧のとおり、アプリのルートに「db」が必要です。

from . base_models import areas
from sqlalchemy.orm import relationship, backref
from ..utils.helper_functions import newid
from .. import db


class Areas(db.Model, areas):
    """Area model class.
    """
    country = relationship("Countries", backref=backref('areas'))

    def __init__(self, *args, **kwargs):
        self.area_id = newid()
        super(Areas, self).__init__(*args, **kwargs)

    def __str__(self):
        return u"{}".format(self.area_name).encode('utf8')

    def __repr__(self):
        return u"<Area: '{}'>".format(self.area_name).encode('utf8')

だから私の質問は、外部で(Flaskまたは別のアプリのいずれかによって)構成でき、それでもアプリケーションファクトリパターンを使用できるdbインスタンスをどのように作成できるのですか?

edit:コード例は正しくありません。Flask-SQLalchemyのインポートがあり、from database import Databaseに置き換えられました。混乱してすみません。

27
Erik Oosterwaal

ほとんどのFlask拡張と同様に、Flask-SQLAlchemy拡張は、ファクトリの外部で作成し、init_appを使用してファクトリで初期化する必要があります。これは、dbオブジェクト。

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    db.init_app(app)
    return app

あなたのFlaskアプリは、適切に設計された他のPythonプロジェクトと同様に、インストール可能なパッケージである必要があります。これは簡単に実行できます。プロジェクトのレイアウトが適切であることを確認してから、基本的なsetup.pyファイルを追加します。

project/
    my_flask_package/
        __init__.py  # at the most basic, this contains create_app and db
    setup.py
from setuptools import setup, find_packages

setup(
    name='my_flask_package',
    version='1.0',
    packages=find_packages(),
    install_requires=['flask', 'flask-sqlalchemy'],
)
$ python setup.py sdist

Flaskアプリをデータベースとともに、他のプロジェクトで使用するためにインストールできます。2番目のプロジェクトのvirtualenvにインストールしてインポートし、アプリを作成してプッシュして初期化します。

$ pip install my_flask_package-1.0.tar.gz
from my_flask_package import db, create_app
create_app().app_context().Push()
db.session.query(...)

アプリケーションの作成に伴うオーバーヘッドが心配な場合は、create_app関数に引数を追加して、何を初期化するかを制御できます。ほとんどの場合、これは問題になりません。

20
davidism

私は同じ問題に出くわしました。

「SQLALCHEMY_ECHO」をオンにすると、おそらく新しいトランザクションが開始されますが、対応するCOMMIT/ROLLBACKが欠落していることがわかります。

私が見つけたものについては、モデルファイルとweb.pyの両方で作成した2つのSQLAlchemyインスタンスと関係があります。最も可能性が高いのは、web.pyのセッションを操作し、モデルにクエリを実行すると、COMMITを受け取るコンテキストが切り替えられるためです。

モデルから「db」をインポートし、db.init_app(app)を呼び出して初期化することで問題を修正しました。ログによると、コミットは正常に機能します。

@app.teardown_appcontext Flask-SQLAlchemyのSQLAlchemyクラスで設定されているため、必要ありません( https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy/init。py

2
stffn

この方向に冒険する他の人々のために。 かなり良いブログ投稿がありますライブラリへのリンク は、SQLAlchemyをFlaskに直接リンクすることなく、Flask-SQLAlchemyのような利点を提供します。

警告の言葉;私はAlchyを使用しようとしましたが、それをFlaskと非Webアプリの両方に統合する方法をまだ完全に理解できなかったため、これに対するデビディズムによって受け入れられた答えを使いましたあなたの走行距離は異なる場合があります。

2
Erik Oosterwaal

簡単に共有できます。方法を紹介します。これを考慮Flaskアプリ:

.
├── config.py
├── db
│   └── test.db
├── do_somenthing2.py ============> Here is run some script 2
├── do_something.py   ============> Here is run some script
├── machinelearning
│   ├── models
│   │   ├── restore.py
│   │   ├── train.py
│   │   └── utils.py
│   └── save
│       └── test.ckpt
├── runserver.py ============> Here is run your app
├── test.py
└── web
    ├── __init__.py
    ├── api
    │   ├── __init__.py
    │   ├── app.py  ============> Here is app = Flask(__name__)
    │   ├── client.py
    │   ├── models.py ==========> Here is db = SQLAlchemy(app)
    │   ├── sample.json
    │   └── utils.py
    └── frontend
        ├── __init__.py
        └── routes.py

runserver.py

import os

from config import DEBUG

from web.api.app import app
from web.api.client import *

if __name__ == "__main__":
    app.run(debug=DEBUG)

OK。ここで、同じモデルを使用して別のことを行います。たとえば、同じモデルを使用して、マシンをトレーニングし、提供してデータベース(ORM)に保存します。

アプリをインポートして、app.test_request_context()を使用できます。そのように:

do_something.py

from web.api.app import app from web.api.models import db、user

def do_something():
    q = db.session.query(User)\
        .filter(User.Name.ilike('Andre'))
    for i in q.all():
        print (i.Name)    

with app.test_request_context():
    do_something()

do_something2.py(実際の例)

from web.api.app import app
from web.api.models import *

def save(df):

    passengers = []

    records = df.to_dict('records')

    for row in records:
        p = Passenger(row)
        passengers.append(p)

    for p in passengers:
        db.session.add(p)

    db.session.commit()

from ml.models import train, restore

with app.test_request_context():
    print ('Trainning model. It will take a while... (~ 5 minutos)')
    train.run()
    print ('Saving model...')
    save(restore.run())
    print ('Saved!')

多くの回答が使用を推奨しています(別のフォルダーからのファイルのインポート):

import sys
sys.path.append('../')

しかし、Flaskアプリやその他のスクリプトを使用している場合は、相対参照の解決に夢中になるため、同意しません。

Flaskアプリとそのデータベースを他のプロジェクトで使用するためにデータベースにインストールする方法は、別のオプションです。

パッケージとモジュール に関するドキュメントをここに見つけることができます。

パッケージは、「ドット付きモジュール名」を使用してPythonのモジュール名前空間を構造化する方法です。たとえば、モジュール名ABは、Aというパッケージ内のBというサブモジュールを指定します。モジュールを使用すると、異なるモジュールの作成者が互いのグローバル変数名を気にする必要がなくなるのと同様に、ドット付きモジュール名を使用すると作成者が保存されますNumPyやPillowのようなマルチモジュールパッケージは、互いのモジュール名を気にする必要がありません。

0
Andre Araujo