Problema de contexto / importación de Flask-SQLAlchemy

117

Quiero estructurar mi aplicación Flask algo como:

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

apps.members es un plano de matraz.

Ahora, para crear las clases modelo, necesito tener la aplicación, algo como:

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

db = SQLAlchemy(current_app)

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

Pero si intento importar ese modelo a mi aplicación Blueprint, obtengo el temido RuntimeError: working outside of request context. ¿Cómo puedo obtener mi aplicación correctamente aquí? Las importaciones relativas pueden funcionar, pero son bastante feas y tienen sus propios problemas de contexto, por ejemplo:

from ...site import app

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

Respuestas:

295

El flask_sqlalchemymódulo no tiene que ser inicializado con la aplicación de inmediato - que puede hacer esto en su lugar:

# apps.members.models
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

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

Y luego, en la configuración de su aplicación, puede llamar a 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 esta forma puede evitar importaciones cíclicas.

Este patrón no requiere que coloques todos tus modelos en un archivo. Simplemente importe la dbvariable en cada uno de los módulos de su modelo.

Ejemplo

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

Nota: este es un esbozo de parte del poder que esto le brinda; obviamente, hay bastante más que puede hacer para facilitar aún más el desarrollo (usando un create_apppatrón, registro automático de planos en ciertas carpetas, etc.)

Sean Vieira
fuente
2
¿Puedes hacer eso varias veces? Por ejemplo, si tengo más de un archivo models.py?
Brad Wright
@BradWright: lo hace más fácil si crea solo una dbinstancia para cada base de datos que tiene. Si tiene un paquete de modelos, puede colocarlo __init__.py. Independientemente de cómo elija hacerlo, simplemente importe la dbvariable desde esa ubicación a sus otros archivos de modelo y úsela normalmente. Cuando se cargan todo se resuelve correctamente.
Sean Vieira
1
¿Tendría un enlace a un proyecto configurado de esta manera?
Mbrevda
4
@Mbrevda: puede ver un ejemplo de este patrón aquí github.com/svieira/Budget-Manager
Sean Vieira
1
El .ext.espacio de nombres está en desuso; es mejor importar desde el espacio de nombres real ( flask_sqlalchemy).
Sean Vieira
25

una 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()

Acabo de dividir una app.py en app.py y model.py sin usar Blueprint. En ese caso, la respuesta anterior no funciona. Se necesita un código de línea para funcionar.

antes :

db.init_app(app)

despues :

db.app = app
db.init_app(app)

Y el siguiente enlace es muy útil.

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

Cybaek
fuente
2
db.app = appobtenía runtimeerror porque init_app no ​​configura la aplicación. +1
Amit Tripathi
3
db.app = app(seguido de db.init_app(app)) fue la pieza que me faltaba. Funciona perfectamente después de agregar esa línea (combinado con la respuesta de Sean Vieira)
Dotl
El primer enlace está desactualizado.
Rahul KP