2015-10-14 9 views
12

Ich habe eine Lauf Flask-Anwendung, die wir online gefunden nach einer Kombination bewährter Verfahren eingerichtet ist und in Miguel Grinberg des „Flask Web Development“ Buch.Teile sqlalchemy Modelle zwischen Kolben und anderen Anwendungen

Wir brauchen jetzt eine zweite Python-Anwendung, die KEINE Web-App ist und Zugriff auf die gleichen Modelle wie die Flask-Anwendung benötigt. Wir wollten dieselben Modelle wiederverwenden, sodass beide Apps vom gemeinsamen Code profitieren können.

Wir haben Abhängigkeiten auf dem Kolben-sqlalchemy extention entfernt (die wir vorher benutzt, wenn wir nur die Flasche Anwendung hatte). Und ersetzt es durch die SQLalchemy Declarative extension described here, die ein bisschen einfacher ist (Flask-SQLalchemy adds a few specific things to standard SQLAlchemy)

In Übereinstimmung mit dem Beispiel haben wir eine Datei datenbank.py im Stamm erstellt. In unserem Fall gibt es zwei Dinge, die sich von der deklarative Erweiterung Beispiel: ich den Motor und Sitzung in einer Klasse setzen, weil alle unsere Modelle db.session verwenden, statt db_session, und gebe ich ein Wörterbuch mit Konfigurationswerten zum init(), damit ich dieses database.py sowohl von Flask als auch von einer anderen Anwendung mit einer anderen Konfiguration wiederverwenden kann. es sieht so aus:

from sqlalchemy import create_engine 
from sqlalchemy.orm import scoped_session, sessionmaker 
from sqlalchemy.ext.declarative import declarative_base 


class Database(object): 

    def __init__(self, cfg): 
     self.engine = create_engine(cfg['SQLALCHEMY_DATABASE_URI'], convert_unicode=True) 
     self.session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=self.engine)) 

    class Model(object): 
     pass 

Base = declarative_base() 

So jetzt kommen wir zum eigentlichen Problem. Flask erstellt ein wörterbuchähnliches Objekt, das Konfigurationsoptionen enthält, und fügt sie als Eigenschaft der App-Instanz hinzu. Es lädt sie aus einem instance folder, einem config.py im Stammverzeichnis der Site und von Umgebungsvariablen. Ich muss das Konfigurationswörterbuch von Flask übergeben, also brauche ich FLASK, um zuerst die Konfiguration zu laden und zu assemblieren, danach die Datenbank zu initialisieren und ein (konfiguriertes) db-Objekt im Stamm der App-Datei zu haben. Wir folgen jedoch der Application factory pattern, so dass wir verschiedene Konfigurationen für verschiedene Situationen (Test, Produktion, Entwicklung) verwenden können.

Dies bedeutet, dass unsere app/__init__.py etwa wie folgt aussieht (vereinfacht):

from flask import Flask 
from database import Database 
from flask.ext.mail import Mail 
from flask_bcrypt import Bcrypt 
from config import config 

mail = Mail() 
bcrypt = Bcrypt() 


def create_app(config_name): 

    app = Flask(__name__, instance_relative_config=True) 

    if not config_name: 
     config_name = 'default' 
    app.config.from_object(config[config_name]) 
    app.config.from_pyfile('config.py') 
    config[config_name].init_app(app) 

    db = Database(app.config) 

    mail.init_app(app) 
    bcrypt.init_app(app) 

    @app.teardown_appcontext 
    def shutdown_session(exception=None): 
     db.session.remove() 

    from main import main as main_blueprint 
    app.register_blueprint(main_blueprint) 

    return app 

Aber die db (dass die Modelle importieren aus ..), muss nun innerhalb der create_app() Funktion, denn das ist, wo Der Kolben lädt die Konfiguration. Wenn ich das db -Objekt außerhalb der Funktion create_app() instanziieren würde, könnte es aus den Modellen importiert werden, aber es ist nicht konfiguriert!

ein Beispiel Modell wie folgt aussieht, und wie man sehen kann, erwartet er einen „db“ in der Wurzel der App:

from . base_models import areas 
from sqlalchemy.orm import relationship, backref 
from ..utils.helper_functions import newid 
from .. import db 


class Areas(db.Model, areas): 
    """Area model class. 
    """ 
    country = relationship("Countries", backref=backref('areas')) 

    def __init__(self, *args, **kwargs): 
     self.area_id = newid() 
     super(Areas, self).__init__(*args, **kwargs) 

    def __str__(self): 
     return u"{}".format(self.area_name).encode('utf8') 

    def __repr__(self): 
     return u"<Area: '{}'>".format(self.area_name).encode('utf8') 

Also meine Frage ist, wie kann ich eine DB-Instanz, kann extern konfiguriert werden (entweder mit Flask oder einer anderen App) und weiterhin das Application Factory Pattern verwenden?

bearbeiten: Das Codebeispiel war falsch, es hatte einen Import für Flask-SQLalchemy, die durch from database import Database ersetzt wurde. Entschuldigung für jede Verwirrung.

+0

Die Teardown-Funktion, ruft 'db.session.remove' unnötig und kann tatsächlich zu Problemen führen. – davidism

+1

Die Teardown-Funktion benötigt die Declarative-Methode gemäß der Flask-Dokumentation: http://flask.pocoo.org/docs/0.10/patterns/sqlalchemy/ –

+1

Da es sich um eine Scoped-Sitzung handelt, ist das nur überflüssig. Danke, dass du das herausgebracht hast, aber ich muss diese Dokumente reparieren. – davidism

Antwort

12

Die Erweiterung Flask-SQLAlchemy sollte wie die meisten Flask-Erweiterungen außerhalb der Fabrik erstellt und dann im Werk unter Verwendung von init_app initialisiert werden. Dies ist so, dass Sie das Objekt db verwenden können, bevor eine App erstellt wird.

db = SQLAlchemy() 

def create_app(): 
    app = Flask(__name__) 
    db.init_app(app) 
    return app 

Ihre Flask app, wie jedes richtig Python-Projekt entwickelt, sollte ein installierbares Paket sein. Dies ist einfach zu tun: Stellen Sie sicher, dass Ihr Projekt Layout sinnvoll ist, dann fügen Sie eine grundlegende setup.py Datei hinzu.

project/ 
    my_flask_package/ 
     __init__.py # at the most basic, this contains create_app and db 
    setup.py 
from setuptools import setup, find_packages 

setup(
    name='my_flask_package', 
    version='1.0', 
    packages=find_packages(), 
    install_requires=['flask', 'flask-sqlalchemy'], 
) 
$ python setup.py sdist 

Jetzt können Sie Ihre Flasche App installieren, zusammen mit ihm Datenbank ist für die Verwendung in anderen Projekten. Installiere und importiere es im Virtualenv deines zweiten Projekts und erstelle dann eine App, um sie zu initialisieren.

$ pip install my_flask_package-1.0.tar.gz 
from my_flask_package import db, create_app 
create_app().app_context().push() 
db.session.query(...) 
+2

Danke @davidism, aber das schafft einen kompletten Flaschenkontext in meinem sekundären Projekt. Das Sekundärprojekt ist ein ausführbarer Hintergrundprozess. Es scheint, dass es etwas übertrieben ist, eine vollständige Flascheninstanz/einen vollständigen Flaschenzug zu haben, nur um die gleichen Datenbankmodelle zu teilen. Ich möchte nur die Modelle trennen, ohne sie unter Flask laufen zu lassen. –

+1

Ich stimme nicht zu. Der Flask-Kontext ist nicht sehr komplex, und Sie gehen genau einmal damit um. Sie verwenden Flask nicht, Sie verwenden nur seine Konfiguration. Ich führe eine Reihe von Hintergrundaufgaben mit Sellerie aus, alles im Kontext meiner App, und hatte nie Probleme. Denken Sie auch darüber nach, was Sie tun möchten, wenn Sie eine E-Mail senden möchten, nachdem eine Aufgabe erledigt ist? Oder generieren Sie Passwörter mit Bcrypt? Es wäre albern, das Rad neu zu erfinden, wenn es bereits definiert und konfiguriert ist. – davidism

+2

hhhmm .. Ich sehe deinen Punkt, aber die andere Seite der Geschichte ist, was passiert, wenn ich * E-Mails * nicht senden oder Bcrypt verwenden muss (was ist wahrscheinlicher)? In diesem Fall gibt es eine Menge Code in meiner Codebasis, die niemals verwendet oder aktualisiert wird. Oder wenn ich eine neuere Version der Bcrypt-Bibliothek in Flask benötige, aber sie ist nicht kompatibel mit meinem sekundären Projekt. Ich hätte lieber separate Projekte, die genau das verwenden/importieren, was sie brauchen, um solche Situationen zu verhindern. –

2

Für andere Menschen in diese Richtung zu wagen. There is quite a good blog post und link to a library, die Flask-SQLAlchemy ähnliche Vorteile bietet, ohne SQLAlchemy direkt mit Flask zu verbinden.

Ein Wort der Warnung jedoch; Ich habe versucht, Alchy zu benutzen, konnte aber immer noch nicht herausfinden, wie ich es in Flask und eine Nicht-Web-App integrieren konnte, also ging ich mit der akzeptierten Antwort des Davidismus auf diese Frage. Ihre Laufleistung kann variieren.

+1

@dgilland kann möglicherweise eine Antwort auf die Integration von Alchy sowohl mit Flask als auch mit einer Nicht-Web-App geben –

1

Ich lief in das gleiche Problem.

Wenn Sie "SQLALCHEMY_ECHO" einschalten, sehen Sie wahrscheinlich, dass eine neue Transaktion gestartet wird, aber das entsprechende COMMIT/ROLLBACK fehlt.

Für was ich herausgefunden habe, hat es etwas mit zwei SQLAlchemy-Instanzen zu tun, die Sie auch einmal in Ihrer Modelldatei und einmal in Ihrem web.py erstellen. Höchstwahrscheinlich liegt es daran, dass Sie mit Ihrer web.py-Sitzung interagieren und wenn Sie Ihre Modelle abfragen, wird ein Kontextwechsel stattfinden, der das COMMIT erhalten wird.

Ich behob das Problem durch Importieren von "db" aus Modellen und dann init durch Aufrufen von db.init_app (app). Laut den Logs funktioniert nun Commit.

Die @app.teardown_appcontext sollte nicht notwendig sein, da es SQLAlchemy Klasse in Flask-SQLAlchemy der eingerichtet ist (https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy/init.py)

Verwandte Themen