Использование Flask-SQLAlchemy в моделях Blueprint без ссылки на приложение
Я пытаюсь создать "модульное приложение" в Flask с помощью Blueprints.
При создании моделей, однако, я сталкиваюсь с проблемой обращения к приложению, чтобы получить db
-объект, предоставленный Flask-SQLAlchemy. Я хотел бы иметь возможность использовать некоторые чертежи с несколькими приложениями (аналогично тому, как приложения Django могут быть использованы), поэтому это нехорошее решение. *
- Можно сделать switcharoo и создать Blueprint для создания экземпляра
db
, который приложение затем импортирует вместе с остальной частью чертежа. Но тогда любой другой проект, желающий создавать модели, должен импортировать из этого проекта вместо приложения.
Мои вопросы таковы:
- Есть ли способ, чтобы Blueprints определял модели без какой-либо осведомленности о приложении, которое они используют в дальнейшем, - и есть ли несколько чертежей вместе? Под этим я подразумеваю необходимость импортировать модуль/пакет приложения из вашего Blueprint.
- Неужели я ошибаюсь с самого начала? Являются ли чертежи не независимыми от приложения и не распространяются (приложения la Django)?
- Если нет, то какой шаблон следует использовать для создания чего-то подобного? Расширения фляг? Если вы просто этого не сделаете - и, возможно, централизовать все модели/схемы à la Ruby on Rails?
Изменить: я уже думал об этом сам, и это может быть больше связано с SQLAlchemy, чем с Flask, потому что при объявлении моделей нужно иметь declarative_base()
. И это должно произойти откуда-то, во всяком случае!
Возможно, лучшим решением является определение схемы проекта в одном месте и распространение ее, как это делает Ruby on Rails. Декларативные определения класса SQLAlchemy действительно больше похожи на schema.rb, чем Django models.py. Я предполагаю, что это также облегчило бы использование миграций (от alembic или sqlalchemy-migrate).
Мне было предложено привести пример, поэтому давайте сделаем что-то простое: скажем, у меня есть план, описывающий "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
Этот вопрос связан с:
И другие, но все ответы, похоже, полагаются на импорт экземпляра app db
или на обратное. Страница "Большое приложение, как" вики-страницы также использует шаблон "импортировать ваше приложение в ваш план".
* Поскольку официальная документация показывает, как создавать маршруты, представления, шаблоны и активы в Blueprint, не заботясь о том, какое приложение оно "включено", я предположил, что Blueprints должны, в общем, быть повторно используемыми в приложениях, Однако эта модульность не кажется полезной без наличия независимых моделей.
Так как Blueprints можно подключить к приложению более одного раза, может быть, это неправильный подход к модели в "Чертежах"?
Ответы
Ответ 1
Я считаю, что самый верный ответ заключается в том, что модульные чертежи не должны напрямую касаться доступа к данным, а вместо этого полагаться на приложение, обеспечивающее совместимую реализацию.
Итак, учитывая ваш примерный проект.
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 и вместо этого просто использовать vanial declarative_base из sqlalchemy, но это должно представлять собой суть этого.
Ответ 2
Чертежи - это скорее логическое разделение проблем, чем фактические приложения.
Экземпляр базы данных должен быть настроен приложением, а не Blueprint, чтобы в одном месте можно было настроить такие параметры, как обработка фона и другие модули. Если Blueprint настроит базу данных, она будет зависеть от остальной части приложения Flask, сделав ее не такой модульной.
Взгляните на https://github.com/masom/Bluemonk для приложения с флагом среднего размера, используя множество различных плагинов Flask и шаблонов проектирования.
Ответ 3
Вы спросили: "Являются ли чертежи не независимыми от приложения и могут быть распространены (приложения la Django)?"
Ответ: да. Чертежи не похожи на приложение Django.
Если вы хотите использовать разные приложения/конфигурации, вам необходимо использовать "Диспетчер приложений", а не чертежи. Прочитай это [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. "
Ответ 4
У меня есть аналогичные потребности в создании чертежей полностью модульных и не имеющих ссылки на приложение. Я придумал, возможно, чистое решение, но я не уверен, насколько он прав и каковы его ограничения.
Идея состоит в создании отдельного объекта db
(db = SQLAlchemy()
) внутри чертежа и вызова методов init_app()
и create_all()
, из которых создается корневое приложение.
Вот пример кода, чтобы показать, как структурирован проект:
Приложение называется jobs
, а чертеж называется status
, и он хранится внутри папки чертежей.
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
Я тестировал его с помощью gunicorn
с gevent
рабочим, и он работает. Я задал отдельный вопрос о надежности решения здесь:
Создайте один экземпляр SQLAlchemy для каждого проекта и вызовите create_all несколько раз