65 votes

Utilisation de Flask-SQLAlchemy dans les modèles Blueprint sans référence à l'application

J'essaie de créer une "application modulaire" dans Flask en utilisant Blueprints.

Cependant, lors de la création de modèles, je me heurte au problème suivant : je dois faire référence à l'application afin d'obtenir l'adresse de l'utilisateur. db -fourni par Flask-SQLAlchemy. J'aimerais pouvoir utiliser certains blueprints avec plus d'une application (de manière similaire à la façon dont les applications Django peuvent être utilisées), ce n'est donc pas une bonne solution.*

  • Il est possible d'inverser les choses et de faire en sorte que le Blueprint crée l'image de l'utilisateur. db que l'application importe ensuite avec le reste du blueprint. Mais alors, tout autre blueprint souhaitant créer des modèles doit importer de que au lieu de l'application.

Mes questions sont donc les suivantes :

  • Existe-t-il un moyen de laisser les Blueprints définir des modèles sans tenir compte de l'application dans laquelle ils sont utilisés par la suite - et de faire en sorte que plusieurs Blueprints se rejoignent ? Je veux dire par là qu'il faut importer le module/paquet de l'application à partir de votre Blueprint.
  • Ai-je tort dès le départ ? Les Blueprints ne sont-ils pas censés être indépendants de l'application et être redistribuables (à la manière des applications Django) ?
    • Si non, alors quel modèle devrait que vous utilisez pour créer quelque chose comme ça ? Des extensions Flask ? Devriez-vous simplement ne pas le faire -- et peut-être centraliser tous les modèles/schémas à la Ruby on Rails ?

Modifier : J'ai réfléchi à cette question moi-même maintenant, et cela pourrait être plus lié à SQLAlchemy qu'à Flask parce que vous devez avoir l' declarative_base() lors de la déclaration des modèles. Et c'est doit bien venir de quelque part, de toute façon !

La meilleure solution consiste peut-être à définir le schéma de votre projet en un seul endroit et à le diffuser, comme le fait Ruby on Rails. Les définitions déclaratives des classes SQLAlchemy ressemblent davantage à schema.rb qu'à models.py de Django. J'imagine que cela rendrait également plus facile l'utilisation des migrations (de alambic 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 des "pages plates" -- un contenu simple et "statique" stocké dans la base de données. Il utilise une table avec juste un nom court (pour les URLs), un titre et un corps. Ceci 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, il serait bien de laisser ce blueprint définir son propre modèle (ceci en 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 d'autres encore, mais toutes les réponses semblent dépendre de l'importation de l'application. db ou l'inverse. Le site "Large app how to" La page wiki utilise également le modèle "importer votre application dans votre blueprint".

* Puisque la documentation officielle montre comment créer des routes, des vues, des modèles et des ressources 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 entre les applications. Cependant, cette modularité ne semble pas <em>que </em>utile sans disposer également de modèles indépendants.

Étant donné que les Blueprints peuvent être reliés à une application plusieurs fois, il se peut que ce soit simplement une mauvaise approche que d'avoir des modèles dans les Blueprints ?

36voto

udoprog Points 978

Je pense que la réponse la plus vraie est que les blueprints modulaires ne devraient pas se préoccuper directement de l'accès aux données, mais plutôt compter sur l'application pour fournir 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 base déclarative vanille de sqlalchemy, mais cela devrait représenter l'essentiel.

7voto

NitRed Points 44

J'ai les mêmes besoins de rendre les Blueprints complètement modulaires et de n'avoir aucune référence à l'application. J'ai trouvé une solution qui pourrait être propre, mais je ne suis pas sûr qu'elle soit correcte et quelles sont ses limites.

L'idée est de créer un espace séparé db objet ( db = SQLAlchemy() ) à l'intérieur du blueprint et appeler le init_app() y create_all() à partir desquelles l'application racine est créée.

Voici un exemple de code pour montrer comment le projet est structuré : L'application s'appelle jobs et le plan s'appelle 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):
    __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

Je l'ai testé avec gunicorn avec gevent travailleur et ça marche. J'ai posé une question distincte sur la robustesse de la solution ici : Créer une instance SQLAlchemy par blueprint et appeler create_all plusieurs fois

6voto

Martin Samson Points 2134

Les Blueprints constituent davantage une séparation logique des préoccupations que des applications réelles.

L'instance de la base de données doit être configurée par l'application et non par un Blueprint pour permettre à des choses comme le traitement en arrière-plan et d'autres modules d'être configurés en un seul endroit. Si un Blueprint configure la base de données, elle devient alors une dépendance du reste de l'application Flask, ce qui la rend moins modulaire.

Jetez un coup d'œil à https://github.com/masom/Bluemonk pour une application Flask de taille moyenne utilisant de nombreux plugins et modèles de conception Flask différents.

0voto

codegeek Points 4282

Vous avez demandé "Les Blueprints ne sont-ils pas censés être indépendants de l'application et être redistribuables (à la Django apps) ? "

La réponse est oui. Les Blueprints ne sont pas similaires à Django App.

Si vous souhaitez utiliser des applications/configurations différentes, vous devez utiliser le "Application Dispatching" et non les blueprints. Lire 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 est clairement dit et je cite "Un blueprint dans Flask n'est pas une application pluggable parce que 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 Application Dispatching), mais vos applications auront des configurations distinctes et seront gérées au niveau de la couche WSGI."

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X