J'essaie de créer une "application modulaire" dans Flask using Blueprints.
Cependant, lors de la création de modèles, je rencontre le problème d'avoir à référencer l'application afin d'obtenir l'objet db
- fourni par Flask-SQLAlchemy. J'aimerais pouvoir utiliser certains plans avec plus d'une application (similaire à la façon dont Django peuvent être utilisées), donc ce n'est pas une bonne solution. *
db
, que l'application importe ensuite avec le reste du blueprint. Mais alors, tout autre plan souhaitant créer des modèles doit importer à partir de ce plan au lieu de l'application.Mes questions sont donc:
Edit: J'y ai pensé moi-même maintenant, et cela pourrait être plus lié à SQLAlchemy qu'à Flask parce que vous devez avoir la fonction
declarative_base()
lors de la déclaration des modèles. Et c'est doit venir de quelque part, de toute façon!La meilleure solution est peut-être de définir le schéma de votre projet en un seul endroit et de le diffuser, comme Ruby on Rails le fait. Les définitions déclaratives de la classe SQLAlchemy sont vraiment plus comme schema.rb que models.py de Django. J'imagine que cela faciliterait également l'utilisation des migrations (de alembic ou sqlalchemy-migrate ).
On m'a demandé de fournir un exemple, alors faisons quelque chose de simple: disons que j'ai un plan décrivant les "pages plates" - un contenu simple et "statique" stocké dans la base de données. Il utilise un tableau avec juste un nom court (pour les URL), un titre et un corps. C'est 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)
Ensuite, ce serait bien de laisser ce plan définir son propre modèle (ceci dans 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
Cette question est liée à:
Et bien d'autres, mais toutes les réponses semblent reposer sur l'importation de l'instance db
de l'application, ou sur l'inverse. La page wiki "Grande application comment" utilise également le modèle "importer votre application dans votre plan".
* Étant donné que la documentation officielle montre comment créer des itinéraires, des vues, des modèles et des actifs dans un Blueprint sans se soucier de l'application dans laquelle il se trouve, j'ai supposé que les Blueprints devraient, en général, être réutilisables dans toutes les applications. Cependant, cette modularité ne semble pas ça utile sans avoir aussi des modèles indépendants.
Étant donné que les Blueprints peuvent être connectés à une application plus d'une fois, cela pourrait simplement être la mauvaise approche d'avoir des modèles dans Blueprints?
Je crois que la réponse la plus vraie est que les plans modulaires ne devraient pas se préoccuper directement de l'accès aux données, mais plutôt s'appuyer sur l'application fournissant une implémentation compatible.
Donc, étant donné votre exemple de plan.
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)
À partir de là, rien ne vous empêche de fournir une implémentation par défaut.
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()
Et dans votre configuration.
app.config["flat_pages.db"] = setup_default_flat_pages_db(db)
Ce qui précède pourrait être rendu plus propre en ne s'appuyant pas sur l'héritage direct de db.Model et en utilisant simplement une Vanilla declarative_base de sqlalchemy, mais cela devrait en représenter l'essentiel.
J'ai des besoins similaires de rendre les plans complètement modulaires et de n'avoir aucune référence à l'application. J'ai trouvé une solution éventuellement propre mais je ne sais pas dans quelle mesure elle est correcte et quelles sont ses limites.
L'idée est de créer un objet db
séparé (db = SQLAlchemy()
) à l'intérieur du plan et d'appeler les méthodes init_app()
et create_all()
à partir desquelles l'application racine est créé.
Voici un exemple de code pour montrer comment le projet est structuré: L'application est appelée jobs
et le plan directeur est appelé status
et il est stocké dans le dossier 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
Je l'ai testé avec gunicorn
avec gevent
worker et ça marche. J'ai posé une question distincte sur la robustesse de la solution ici: Créez une instance SQLAlchemy par blueprint et appelez create_all plusieurs fois
Vous avez demandé "Les plans ne sont-ils pas censés être indépendants de l'application et être redistribuables (à la Django apps)?"
La réponse est oui. Les plans ne sont pas similaires à Django App.
Si vous souhaitez utiliser différentes applications/configurations, vous devez utiliser "Application Dispatching" et non des plans. Lisez ceci [1]: http://flask.pocoo.org/docs/patterns/appdispatch/#app-dispatch [1]
Aussi, le lien ici [1] http://flask.pocoo.org/docs/blueprints/#the-concept-of-blueprints [1]
Il dit clairement et je cite "Un plan en Flask n'est pas une application enfichable car ce n'est pas réellement une application - c'est un ensemble d'opérations qui peuvent être enregistrées sur une application, même plusieurs fois. Pourquoi ne pas avoir plusieurs objets d'application? Vous pouvez le faire (voir Répartition des applications), mais vos applications auront des configurations distinctes et seront gérées au niveau de la couche WSGI. "