webentwicklung-frage-antwort-db.com.de

Teilen Sie sqlalchemy-Modelle zwischen Flaschen und anderen Apps

Ich habe eine Flask-Anwendung, die gemäß einer Kombination von Best Practices, die wir online und in Miguel Grinbergs "Flask Web Development" -Buch gefunden haben, eingerichtet ist.

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

Wir haben Abhängigkeiten von der Erweiterung "Flask-sqlalchemy" (die wir zuvor verwendet haben, als wir nur die Flask-Anwendung hatten) entfernt. Und ersetzte es durch die hier beschriebene SQLalchemy-Deklarationserweiterung , die etwas einfacher ist ( Flask-SQLalchemy fügt der Standard-SQLAlchemy einige Besonderheiten hinzu.)

In Übereinstimmung mit dem Beispiel haben wir eine Datei "database.py" im Stammverzeichnis erstellt. In unserem Fall unterscheiden sich zwei Dinge vom deklarativen Erweiterungsbeispiel: Ich habe die Engine und die Sitzung in eine Klasse eingefügt, da alle unsere Modelle db.session anstelle von db_session verwenden, und ich übergebe ein Wörterbuch mit Konfigurationswerten an init () , damit ich diese database.py sowohl von Flask als auch von einer anderen Anwendung mit einer anderen Konfiguration wiederverwenden kann. es sieht aus wie das:

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

Nun kommen wir zum eigentlichen Problem. Flask erstellt ein wörterbuchartiges Objekt, das Konfigurationsoptionen enthält, und fügt sie der App-Instanz als Eigenschaft hinzu. Es lädt sie aus einem Instanzordner , einer config.py im Stammverzeichnis der Site und aus Umgebungsvariablen. Ich muss das Konfigurationswörterbuch von Flask übergeben, also muss ich Flask erst laden und zusammenbauen, danach die Datenbank initialisieren und ein (konfiguriertes) db-Objekt im Stammverzeichnis der App-Datei haben. Wir folgen jedoch dem Application Factory-Muster , so dass wir unterschiedliche Konfigurationen für verschiedene Situationen (Test, Produktion, Entwicklung) verwenden können. 

Das heißt, unser app/__init__.py sieht ungefähr so ​​aus (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 Datenbankdatei (die die Modelle aus .. importieren) muss jetzt in der Funktion create_app () enthalten sein, da Flask hier die Konfiguration lädt. Wenn ich das db-Objekt außerhalb der create_app () - Funktion instanziieren würde, kann es aus den Modellen importiert werden, es ist jedoch nicht konfiguriert!

ein Beispielmodell sieht so aus, und wie Sie sehen, erwartet es ein "db" im Stammverzeichnis 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')

Meine Frage ist also, wie kann ich eine Datenbankinstanz haben, die extern konfiguriert werden kann (entweder von Flask oder einer anderen App) und trotzdem das Application Factory Pattern verwenden? 

edit: Das Codebeispiel war falsch, es wurde ein Import für Flask-SQLalchemy erstellt, der durch from database import Database ersetzt wurde. Entschuldigung für jede Verwirrung.

19
Erik Oosterwaal

Die Flask-SQLAlchemy-Erweiterung sollte wie die meisten Flask-Erweiterungen außerhalb der Factory erstellt und dann in der Factory mit init_app initialisiert werden. Auf diese Weise können Sie das db-Objekt verwenden, bevor eine App erstellt wird.

db = SQLAlchemy()

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

Ihre Flask-App sollte wie jedes ordnungsgemäß entworfene Python-Projekt ein installierbares Paket sein. Dies ist einfach: Vergewissern Sie sich, dass Ihr Projektlayout sinnvoll ist, und fügen Sie dann 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 Flask-App zusammen mit ihrer Datenbank für andere Projekte installieren. Installieren und importieren Sie es in der virtuellen Umgebung Ihres zweiten Projekts. Erstellen Sie dann eine App und drücken Sie Push, 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(...)

Wenn Sie Bedenken hinsichtlich des Aufwands haben, der beim Erstellen Ihrer Anwendung anfällt, können Sie der create_app-Funktion Argumente hinzufügen, um zu steuern, was initialisiert wird. In den meisten Fällen sollte dies jedoch kein Problem sein.

19
davidism

Ich bin auf das gleiche Problem gestoßen.

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

Für das, was ich herausgefunden habe, hat es etwas mit zwei SQLAlchemy-Instanzen zu tun, die Sie auch erstellen, einmal in Ihrer Modelldatei und einmal in Ihrer Datei web.py. Wahrscheinlich liegt es daran, dass Sie mit der Sitzung Ihres web.py interagieren. Wenn Sie Ihre Modelle abfragen, wird ein Kontextwechsel durchgeführt, der das COMMIT empfängt.

Ich habe das Problem behoben, indem "db" aus Modellen importiert und dann durch Aufruf von db.init_app (app) initiiert wurde. Laut den Protokollen funktioniert das Festschreiben jetzt gut.

Der @app.teardown_appcontext sollte nicht erforderlich sein, da er in der SQLAlchemy-Klasse von Flask-SQLAlchemy ( https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy/ init . Py) eingerichtet ist )

2
stffn

Für andere, die sich in diese Richtung wagen. Es gibt einen guten Blogbeitrag und einen Link zu einer Bibliothek , der Flask-SQLAlchemy-ähnliche Vorteile bietet, ohne SQLAlchemy direkt mit Flask zu verknüpfen. 

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

2
Erik Oosterwaal

Sie können leicht teilen. Ich werde zeigen wie. Betrachten Sie diese Flask-App:

.
├── config.py
├── db
│   └── test.db
├── do_somenthing2.py ============> Here is run some script 2
├── do_something.py   ============> Here is run some script
├── machinelearning
│   ├── models
│   │   ├── restore.py
│   │   ├── train.py
│   │   └── utils.py
│   └── save
│       └── test.ckpt
├── runserver.py ============> Here is run your app
├── test.py
└── web
    ├── __init__.py
    ├── api
    │   ├── __init__.py
    │   ├── app.py  ============> Here is app = Flask(__name__)
    │   ├── client.py
    │   ├── models.py ==========> Here is db = SQLAlchemy(app)
    │   ├── sample.json
    │   └── utils.py
    └── frontend
        ├── __init__.py
        └── routes.py

runserver.py

import os

from config import DEBUG

from web.api.app import app
from web.api.client import *

if __== "__main__":
    app.run(debug=DEBUG)

OK. Jetzt möchten Sie die gleichen Modelle verwenden, um etwas anderes zu tun. Zum Beispiel: Trainieren einer Maschine, Servieren und Speichern in einer Datenbank (ORM) mit denselben Modellen.

Sie können die App importieren und app.test_request_context () verwenden. So wie das:

do_something.py

von web.api.app importieren app von web.api.models import db, benutzer

def do_something():
    q = db.session.query(User)\
        .filter(User.Name.ilike('Andre'))
    for i in q.all():
        print (i.Name)    

with app.test_request_context():
    do_something()

do_something2.py (reales Beispiel)

from web.api.app import app
from web.api.models import *

def save(df):

    passengers = []

    records = df.to_dict('records')

    for row in records:
        p = Passenger(row)
        passengers.append(p)

    for p in passengers:
        db.session.add(p)

    db.session.commit()

from ml.models import train, restore

with app.test_request_context():
    print ('Trainning model. It will take a while... (~ 5 minutos)')
    train.run()
    print ('Saving model...')
    save(restore.run())
    print ('Saved!')

Viele Antworten empfehlen die Verwendung (Importieren von Dateien aus verschiedenen Ordnern):

import sys
sys.path.append('../')

Ich stimme jedoch nicht zu, wenn Sie eine Flask-App und andere Skripte haben, da Sie beim Auflösen der relativen Referenzen verrückt werden. 

Der Ansatz, Ihre Flask-App zusammen mit ihrer Datenbank für andere Projekte zu installieren, ist eine weitere Option.

Hier finden Sie eine Dokumentation zu den Paketen und Modulen .

Pakete stellen eine Möglichkeit dar, den Modul-Namespace von Python mithilfe von "Gepunkteten Modulnamen" zu strukturieren. Beispielsweise bezeichnet der Modulname AB ein Submodul mit dem Namen B in einem Paket mit dem Namen A. Genau wie die Verwendung der Module Erspart es den Autoren verschiedener Module, sich um jedes Gedanken zu machen. Bei den globalen Variablennamen anderer, die Verwendung von gepunkteten Modulnamen erspart den Autoren von Multimodulpaketen wie NumPy oder Pillow , um sich um die Modulnamen des jeweils anderen zu kümmern.

0
Andre Araujo