Flask-SQLAlchemy gebruiken in Blueprint-modellen zonder verwijzing naar de app

Ik probeer een “modulaire applicatie” te maken in Flask met behulp van Blueprints.

Bij het maken van modellen loop ik echter tegen het probleem aan dat ik naar de app moet verwijzen om het db-object van Flask-SQLAlchemy te krijgen. Ik zou graag enkele blauwdrukken met meer dan één app willen kunnen gebruiken (vergelijkbaar met hoe Django-apps kunnen worden gebruikt), dus dit is geen goede oplossing.*

  • Het is mogelijk om een ​​switcharoo uit te voeren en de Blueprint de db-instantie te laten maken, die de app vervolgens samen met de rest van de blueprint importeert. Maar elke andere blauwdruk die modellen wil maken, moet importeren uit dieblauwdruk in plaats van de app.

Mijn vragen zijn als volgt:

  • Is er een manier om Blueprints modellen te laten definiëren zonder enig besef van de app waarin ze later worden gebruikt — en dat er meerdere Blueprints bij elkaar komen? Hiermee bedoel ik dat je de app-module/-pakket uit je blauwdruk moet importeren.
  • Ben ik vanaf het begin verkeerd? Zijn blauwdrukken niet bedoeld om onafhankelijk van de app te zijn en herdistribueerbaar te zijn (à la Django-apps)?
    • Zo niet, welk patroon moetu dan gebruiken om zoiets te maken? Flacon extensies? Zou je het gewoon niet moeten doen — en misschien alle modellen/schema’s à la Ruby on Rails centraliseren?

Bewerken: ik heb hier nu zelf over nagedacht, en dit kan meer te maken hebben met SQLAlchemy dan met Flask, omdat je de declarative_base()moet hebben bij het declareren modellen. En datmoet toch ergens vandaan komen!

Misschien is de beste oplossing om het schema van je project op één plek te definiëren en te verspreiden, zoals Ruby on Rails doet. Declaratieve SQLAlchemy-klassedefinities lijken meer op schema.rb dan op Django’s models.py. Ik kan me voorstellen dat dit het gebruik van migraties (van alembicof sqlalchemy-migrate).


Ik werd gevraagd om een ​​voorbeeld te geven, dus laten we iets simpels doen: stel dat ik een blauwdruk heb die “flatpages” beschrijft — eenvoudige, “statische” inhoud opgeslagen in de database. Het gebruikt een tabel met alleen een korte naam (voor URL’s), een titel en een hoofdtekst. Dit is 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)

Dan zou het leuk zijn om deze blauwdruk zijn eigen model te laten definiëren (dit in 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

Deze vraag heeft betrekking op:

En diverse andere, maar alle antwoorden lijken erop te vertrouwen dat de db-instantie van de app wordt geïmporteerd, of omgekeerd. De “Grote app how to”wiki-pagina gebruikt ook de ” importeer uw app in uw blauwdruk”-patroon.

* Aangezien de officiële documentatie laat zien hoe je routes, weergaven, sjablonen en middelen in een blauwdruk kunt maken zonder je zorgen te maken over de app waarin deze zich bevindt, ben ik ervan uitgegaan dat blauwdrukken over het algemeen herbruikbaar moeten zijn in alle apps . Deze modulariteit lijkt echter niet zonuttig zonder ook onafhankelijke modellen te hebben.

Aangezien Blueprints meer dan eens aan een app kunnen worden gekoppeld, is het misschien gewoon de verkeerde benadering om modellen in Blueprints te hebben?


Antwoord 1, autoriteit 100%

Ik geloof dat het beste antwoord is dat modulaire blauwdrukken zich niet rechtstreeks moeten bezighouden met gegevenstoegang, maar in plaats daarvan moeten vertrouwen op de toepassing die een compatibele implementatie biedt.

Dus gezien je voorbeeldblauwdruk.

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)

Hieruit weerhoudt niets u ervan om een ​​standaardimplementatie aan te bieden.

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

En in jouw configuratie.

app.config["flat_pages.db"] = setup_default_flat_pages_db(db)

Het bovenstaande zou schoner kunnen worden gemaakt door niet te vertrouwen op directe overerving van db.Model en in plaats daarvan gewoon een vanilla declarative_base van sqlalchemy te gebruiken, maar dit zou de essentie ervan moeten vertegenwoordigen.


Antwoord 2, autoriteit 17%

Ik heb vergelijkbare behoeften om Blueprints volledig modulair te maken en zonder verwijzing naar de app. Ik heb een mogelijk schone oplossing bedacht, maar ik weet niet zeker hoe correct deze is en wat de beperkingen zijn.

Het idee is om een ​​apart db-object (db = SQLAlchemy()) in de blauwdruk te maken en de init_app()aan te roepen en create_all()methoden van waaruit de root-app is gemaakt.

Hier is wat voorbeeldcode om te laten zien hoe het project is gestructureerd:
De app heet jobsen de blauwdruk heet statusen wordt opgeslagen in de map met blauwdrukken.

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

Ik heb het getest met gunicornmet geventWerknemer en het werkt. Ik vroeg hier een afzonderlijke vraag over de robuustheid van de oplossing hier:
Maak een Sqlalchemy-instantie per blauwdruk en bel Create_all meerdere keren


Antwoord 3

U vroeg “zijn blauwdrukken die niet zijn bedoeld om onafhankelijk van de app te zijn en worden herdistribueerbaar (à la django-apps)?”

Het antwoord is ja. Blauwdrukken zijn niet vergelijkbaar met Django-app.

Als u verschillende app / configuraties wilt gebruiken, moet u “Toepassing Dispatching” gebruiken en geen blauwdrukken. Lees dit
[1]: http://flask.pocoo.org/docs/patterns/ AppDispatch / # App-Dispatch [1]

Ook de link hier [1] http: // fles .pocoo.org / documenten / blauwdrukken / # het-concept-of-blueprints [1]

Het zegt duidelijk en ik citeer “een blauwdruk in fles is geen pluggable-app omdat het niet echt een aanvraag is – het is een reeks operaties die op een applicatie kunnen worden geregistreerd, zelfs meerdere keren. Waarom geen meerdere toepassingsobjecten. ? U kunt dat doen (zie Dispatching van toepassing), maar uw applicaties hebben afzonderlijke configs en worden in de WSGI-laag beheerd. “

Other episodes