Доля моделей sqlalchemy между флягой и другими приложениями

У меня есть запущенное приложение Flask, которое настроено в соответствии с комбинацией лучших практик, которые мы нашли в Интернете, и в книге Мигеля Гринберга "Flask Web Development".

Теперь нам нужно второе приложение Python, которое не является веб-приложением и нуждается в доступе к тем же моделям, что и приложение Flask. Мы хотели повторно использовать те же самые модели, поэтому оба приложения могут извлечь выгоду из общего кода.

Мы удалили зависимости от расширений кол-sqlalchemy (которые мы использовали ранее, когда у нас было только приложение Flask). И заменил его на декларативное расширение SQLalchemy, описанное здесь, что немного проще (Flask-SQLalchemy добавляет несколько конкретных вещей для стандартная SQLAlchemy)

В соответствии с примером мы создали файл database.py в корне. В нашем случае есть две вещи, отличные от примера декларативного расширения: я помещаю двигатель и сеанс в класс, потому что во всех наших моделях используется 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()

Итак, теперь мы приходим к реальной проблеме. Флажок создает объект, подобный словарю, содержащий параметры конфигурации, и добавляет их как свойство в экземпляр приложения. Он загружает их из папки экземпляров, config.py в корневой каталог сайта и из переменных среды, Мне нужно передать в словарь конфигурации из Flask, так что мне нужно загружать Flask в FIRST и собирать конфигурацию, а после этого инициализировать базу данных и иметь (настроенный) db-объект в корне файла приложения. Однако мы следуем шаблону Application factory, поэтому мы можем использовать разные конфигурации для разных ситуаций (тест, производство, разработка).

Это означает, что наш 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')

Итак, мой вопрос: как я могу создать экземпляр db, который может быть настроен извне (с помощью Flask или другого приложения) и все еще использовать шаблон Application factory?

edit: Пример кода был неправильным, у него был импорт для Flask-SQLalchemy, который был заменен на from database import Database. Извините за любую путаницу.

Ответ 1

Расширение Flask-SQLAlchemy, как и большинство расширений Flask, должно быть создано за пределами factory, а затем инициализировано в factory с помощью 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 вместе с его базой данных для использования в других проектах. Установите и импортируйте его во второй виртуальный проект проекта, затем создайте и нажмите приложение для его инициализации.

$ 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(...)

Ответ 2

Для других людей, занимающихся этим направлением. Существует неплохая запись в блоге и ссылка на библиотеку, который предлагает Flask-SQLAlchemy как преимущества, без прямого связывания SQLAlchemy с Flask.

Слово предупреждения, однако; Я пытался использовать Alchy, но все еще не мог понять, как интегрировать его в Flask и не-web-приложение, поэтому я пошел с принятым ответом давидизма на этот вопрос. Ваш пробег может отличаться.

Ответ 3

Я столкнулся с той же проблемой.

Если вы включите "SQLALCHEMY_ECHO", вы, скорее всего, увидите, что новая транзакция запущена, но отсутствует соответствующий COMMIT/ROLLBACK.

Для того, что я узнал, он имеет какое-то отношение к двум экземплярам SQLAlchemy, которые вы также создаете, один раз в вашем файле модели и один раз в своем web.py. Скорее всего, это связано с тем, что вы взаимодействуете с вашим сеансом web.py, и если вы запрашиваете свои модели, есть некоторый контекст, который будет получать COMMIT.

Я исправил проблему, импортировав "db" из моделей, а затем запустил ее, вызвав db.init_app (приложение). Согласно журналам, теперь выполняется работа finde.

@app.teardown_appcontext не требуется, поскольку он настроен в классе SQLAlchemy класса Flask-SQLAlchemy (https://github.com/mitsuhiko/колба-SQLAlchemy/блобо/ведущий/flask_sqlalchemy/ <сильного > INIT.py)