Problem beim Import / Kontext von Flask-SQLAlchemy

117

Ich möchte meine Flask-App so strukturieren:

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

apps.members ist ein Flask Blueprint.

Um die Modellklassen zu erstellen, muss ich die App in der Hand haben, etwa:

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

db = SQLAlchemy(current_app)

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

Aber wenn ich versuche, dieses Modell in meine Blueprint-App zu importieren, bekomme ich das gefürchtete RuntimeError: working outside of request context. Wie kann ich hier richtig auf meine App zugreifen? Relative Importe funktionieren möglicherweise, sind aber ziemlich hässlich und haben ihre eigenen Kontextprobleme, z.

from ...site import app

# ValueError: Attempted relative import beyond toplevel package
Brad Wright
quelle

Antworten:

294

Das flask_sqlalchemyModul muss nicht sofort mit der App initialisiert werden - Sie können dies stattdessen tun:

# apps.members.models
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

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

Und dann können Sie in Ihrem Anwendungs-Setup Folgendes aufrufen init_app:

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

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

Auf diese Weise können Sie zyklische Importe vermeiden.

Dieses Muster erfordert nicht, dass Sie alle Ihre Modelle in einer Datei platzieren. Importieren Sie einfach die dbVariable in jedes Ihrer Modellmodule.

Beispiel

# 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)

Hinweis: Dies ist eine Skizze eines Teils der Leistung, die Sie dadurch erhalten. Offensichtlich können Sie noch einiges mehr tun, um die Entwicklung noch einfacher zu gestalten (mithilfe eines create_appMusters, automatische Registrierung von Blaupausen in bestimmten Ordnern usw.).

Sean Vieira
quelle
2
Schaffst du das mehrmals? Zum Beispiel, wenn ich mehr als eine models.py-Datei habe?
Brad Wright
@BradWright - es macht es einfacher, wenn Sie nur eine dbInstanz für jede Datenbank erstellen , die Sie haben. Wenn Sie ein Modellpaket haben, können Sie es einlegen __init__.py. Wie auch immer Sie sich dafür entscheiden, Sie importieren die dbVariable einfach von diesem Speicherort in Ihre anderen Modelldateien und verwenden sie wie gewohnt. Wenn sie geladen sind, wird alles korrekt aufgelöst.
Sean Vieira
1
Hätten Sie zufällig einen Link zu einem Projekt, das auf diese Weise eingerichtet wurde?
Mbrevda
4
@Mbrevda - Sie können ein Beispiel dieses Musters hier sehen github.com/svieira/Budget-Manager
Sean Vieira
1
Der .ext.Namespace ist veraltet - es ist besser, aus dem realen Namespace ( flask_sqlalchemy) zu importieren .
Sean Vieira
25

ein Original app.py : 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()

Ich habe nur eine app.py in app.py und model.py aufgeteilt, ohne Blueprint zu verwenden. In diesem Fall funktioniert die obige Antwort nicht. Zum Arbeiten wird ein Zeilencode benötigt.

Vor :

db.init_app(app)

nach dem :

db.app = app
db.init_app(app)

Der folgende Link ist sehr nützlich.

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

Cybaek
quelle
2
db.app = appbekam Laufzeitfehler, weil init_app die App nicht setzt. +1
Amit Tripathi
3
db.app = app(gefolgt von db.init_app(app)) war das fehlende Stück für mich. Funktioniert perfekt nach dem Hinzufügen dieser Zeile (kombiniert mit Sean Vieiras Antwort)
Dotl
Erster Link ist veraltet.
Rahul KP