Problème d'importation / de contexte Flask-SQLAlchemy

117

Je souhaite structurer mon application Flask quelque chose comme:

./site.py
./apps/members/__init__.py
./apps/members/models.py

apps.members est un plan de flacon.

Maintenant, pour créer les classes de modèle, j'ai besoin d'avoir une emprise sur l'application, quelque chose comme:

# apps.members.models
from flask import current_app
from flaskext.sqlalchemy import SQLAlchemy

db = SQLAlchemy(current_app)

class Member(db.Model):
    # fields here
    pass

Mais si j'essaie d'importer ce modèle dans mon application Blueprint, j'obtiens le redouté RuntimeError: working outside of request context. Comment puis-je accéder correctement à mon application ici? Les importations relatives peuvent fonctionner mais elles sont assez laides et ont leurs propres problèmes de contexte, par exemple:

from ...site import app

# ValueError: Attempted relative import beyond toplevel package
Brad Wright
la source

Réponses:

295

Le flask_sqlalchemymodule n'a pas besoin d'être initialisé avec l'application tout de suite - vous pouvez le faire à la place:

# apps.members.models
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Member(db.Model):
    # fields here
    pass

Et puis dans la configuration de votre application, vous pouvez appeler init_app:

# apps.application.py
from flask import Flask
from apps.members.models import db

app = Flask(__name__)
# later on
db.init_app(app)

De cette façon, vous pouvez éviter les importations cycliques.

Ce modèle ne nécessite pas de placer tous vos modèles dans un seul fichier. Importez simplement la dbvariable dans chacun de vos modules de modèle.

Exemple

# apps.shared.models
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

# apps.members.models
from apps.shared.models import db

class Member(db.Model):
    # TODO: Implement this.
    pass

# apps.reporting.members
from flask import render_template
from apps.members.models import Member

def report_on_members():
    # TODO: Actually use arguments
    members = Member.filter(1==1).all()
    return render_template("report.html", members=members)

# apps.reporting.routes
from flask import Blueprint
from apps.reporting.members import report_on_members

reporting = Blueprint("reporting", __name__)

reporting.route("/member-report", methods=["GET","POST"])(report_on_members)

# apps.application
from flask import Flask
from apps.shared import db
from apps.reporting.routes import reporting

app = Flask(__name__)
db.init_app(app)
app.register_blueprint(reporting)

Remarque: ceci est une esquisse d'une partie de la puissance que cela vous donne - il y a évidemment beaucoup plus que vous pouvez faire pour rendre le développement encore plus facile (en utilisant un create_appmodèle, des plans à enregistrement automatique dans certains dossiers, etc.)

Sean Vieira
la source
2
Pouvez-vous faire cela plusieurs fois? Par exemple, si j'ai plus d'un fichier models.py?
Brad Wright
@BradWright - cela facilite la tâche si vous créez uniquement une dbinstance pour chaque base de données que vous avez. Si vous avez un ensemble de modèles, vous pouvez le mettre __init__.py. Quelle que soit la manière dont vous choisissez de le faire, il vous suffit d'importer la dbvariable de cet emplacement dans vos autres fichiers de modèle et de l'utiliser normalement. Lorsqu'ils sont chargés, tout se résout correctement.
Sean Vieira
1
Auriez-vous un lien vers un projet mis en place de cette façon?
Mbrevda
4
@Mbrevda - vous pouvez voir un exemple de ce modèle ici github.com/svieira/Budget-Manager
Sean Vieira
1
L' .ext.espace de noms est obsolète - il est préférable d'importer depuis l'espace de noms réel ( flask_sqlalchemy).
Sean Vieira
25

un app.py original : https://flask-sqlalchemy.palletsprojects.com/en/2.x/quickstart/

...

app = flask.Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = flask.ext.sqlalchemy.SQLAlchemy(app)

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
...

class Computer(db.Model):
    id = db.Column(db.Integer, primary_key=True)
...

# Create the database tables.
db.create_all()

...

# start the flask loop
app.run()

Je viens de diviser un app.py en app.py et model.py sans utiliser Blueprint. Dans ce cas, la réponse ci-dessus ne fonctionne pas. Un code de ligne est nécessaire pour fonctionner.

avant :

db.init_app(app)

après :

db.app = app
db.init_app(app)

Et, le lien suivant est très utile.

http://piotr.banaszkiewicz.org/blog/2012/06/29/flask-sqlalchemy-init_app/

cybaek
la source
2
db.app = appobtenait une erreur d'exécution car init_app ne définit pas l'application. +1
Amit Tripathi
3
db.app = app(suivi de db.init_app(app)) était la pièce manquante pour moi. Fonctionne parfaitement après avoir ajouté cette ligne (combinée à la réponse de Sean Vieira)
Dotl
Le premier lien est obsolète.
Rahul KP