web-dev-qa-db-fra.com

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 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. *

  • Il est possible de faire un switcharoo et de demander au Blueprint de créer l'instance 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:

  • Existe-t-il un moyen de laisser Blueprints définir des modèles sans avoir connaissance de l'application dans laquelle ils seront utilisés plus tard - et de réunir plusieurs Blueprints? Par cela, je veux dire avoir à importer le module/package d'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 Django apps)?
    • Sinon, quel modèle devrait utiliser pour créer quelque chose comme ça? Flask extensions? Ne devriez-vous simplement pas le faire - et peut-être centraliser tous les modèles/schémas à la Ruby on Rails?

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?

63
vicvicvic

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.

35
udoprog

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

2
nitred

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. "

0
codegeek