2015-10-14 10 views
12

Ho un'applicazione Flask in esecuzione che è impostata in base a una combinazione delle migliori pratiche che abbiamo trovato online e nel libro "Flask Web Development" di Miguel Grinberg.Condividere i modelli sqlalchemy tra il pallone e altre app

Ora abbiamo bisogno di una seconda applicazione Python, che NON sia un'app Web, e che abbia bisogno di accedere agli stessi modelli dell'applicazione Flask. Volevamo riutilizzare gli stessi modelli di corso, in modo che entrambe le app possano beneficiare del codice condiviso.

Abbiamo rimosso le dipendenze dall'estensione di flask-sqlalchemy (che abbiamo usato prima, quando avevamo solo l'applicazione Flask). E lo ha sostituito con lo SQLalchemy Declarative extension described here, che è un po 'più semplice (Flask-SQLalchemy adds a few specific things to standard SQLAlchemy)

In linea con l'esempio, abbiamo creato un file database.py nella radice. Nel nostro caso ci sono due cose diverse dall'esempio di estensione dichiarativa: metto il motore e la sessione in una classe, perché tutti i nostri modelli usano db.session, invece di db_session, e io passo un dizionario con i valori di configurazione all'iniziativa (), in modo da poter riutilizzare questo database.py sia da Flask che da un'altra applicazione, utilizzando una configurazione diversa. assomiglia a questo:

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

Così ora veniamo al problema reale. Flask crea un oggetto simile al dizionario che contiene le opzioni di configurazione e le aggiunge come proprietà all'istanza dell'app. Li carica da un instance folder, un config.py nella radice del sito e dalle variabili di ambiente. Ho bisogno di passare il dizionario di configurazione da Flask, quindi ho bisogno di Flask su FIRST caricare e assemblare la configurazione, e dopo che inizializzare il database, e avere un oggetto db (configurato) nella root del file app. Tuttavia, seguiamo lo Application factory pattern, quindi possiamo utilizzare diverse configurazioni per situazioni diverse (test, produzione, sviluppo).

Questo significa che il nostro app/__init__.py sembra qualcosa di simile (semplificato):

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 

Ma il db (che i modelli importazione da ..), ora ha bisogno di essere all'interno della funzione create_app(), perché è lì che Flask carica la configurazione. Se istanzassi l'oggetto db al di fuori della funzione create_app(), sarà importabile dai modelli, ma non è configurato!

un modello di esempio si presenta, e come si può vedere, si aspetta un "db" nella root del 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') 

Quindi la mia domanda è: come posso avere un DB di istanza che può essere configurato esternamente (da Flask o da un'altra app), e utilizzare ancora il Pattern di fabbrica dell'applicazione?

modifica: L'esempio di codice non era corretto, aveva un'importazione per Flask-SQLalchemy che è stata sostituita da from database import Database. Ci scusiamo per qualsiasi confusione.

+0

La funzione teardown che chiama 'db.session.remove' non è necessaria e può effettivamente causare problemi. – davidism

+1

La funzione di teardown è di tipo nessario usando il metodo dichiarativo, secondo la documentazione di Flask: http://flask.pocoo.org/docs/0.10/patterns/sqlalchemy/ –

+1

Poiché si tratta di una sessione con ambito, è ridondante. Grazie per averlo sottolineato, ho bisogno di correggere quei documenti. – davidism

risposta

12

L'estensione Flask-SQLAlchemy, come la maggior parte delle estensioni del pallone, deve essere creata all'esterno della fabbrica, quindi inizializzata in fabbrica utilizzando init_app. In questo modo è possibile utilizzare l'oggetto db prima che venga creata un'app.

db = SQLAlchemy() 

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

L'app Flask, come qualsiasi progetto Python progettato correttamente, dovrebbe essere un pacchetto installabile. È semplice: assicurati che il layout del tuo progetto abbia senso, quindi aggiungi un file base setup.py.

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 

Ora è possibile installare il Flask app, insieme con la sua banca dati, per l'utilizzo in altri progetti. Installalo e importalo nel virtualenv del tuo secondo progetto, quindi crea e spinge un'applicazione per inizializzarlo.

$ 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

Grazie a @davidism, ma questo crea un contesto di bottiglia completo all'interno del mio progetto secondario. Il progetto secondario è un processo in background eseguibile. Sembra che avere a disposizione un'istanza/contesto di una bottiglia completa solo per condividere gli stessi modelli di database sia un po 'eccessivo. Vorrei separare solo i modelli, senza eseguirli sotto Flask. –

+1

Non sono d'accordo. Il contesto di Flask non è molto complesso e lo gestisci esattamente una volta. In realtà non stai usando Flask, stai solo usando la sua configurazione. Gestisco una serie di attività in background con Celery, il tutto nel contesto della mia app, e non ho mai avuto problemi. Considerare anche, cosa succede se si vuole fare qualcosa come inviare e-mail dopo che un compito è finito? O generare password usando Bcrypt? Sarebbe sciocco continuare a reinventare la ruota quando è già definita e configurata. – davidism

+2

hhhmm .. Vedo il tuo punto, ma l'altro lato della storia è cosa devo fare se * non * ho bisogno di inviare e-mail o usare Bcrypt (che è più probabile)? In quel caso c'è un mucchio di codice nella mia base di codice che non viene mai usato o aggiornato. O se ho bisogno di una versione più recente della libreria Bcrypt in Flask, ma è incompatibile con il mio progetto secondario. Preferirei avere progetti separati che usino/importino esattamente ciò di cui hanno bisogno, per prevenire tali situazioni. –

2

Per le altre persone che si avventurano in questa direzione. There is quite a good blog post e uno link to a library che offre vantaggi di Flask-SQLAlchemy, senza collegare SQLAlchemy a Flask direttamente.

Tuttavia, una parola di avvertimento; Ho provato ad usare Alchy, ma ancora non riuscivo a capire come integrarlo in Flask e in un'applicazione non web, quindi sono andato con la risposta accettata da davidism a questa domanda. Il tuo chilometraggio può variare.

+1

@dgilland potrebbe essere in grado di fornire una risposta sull'integrazione di Alchy sia con Flask che con un'app non Web –

1

Mi sono imbattuto nello stesso problema.

Se si attiva "SQLALCHEMY_ECHO" è probabile che si acceda che è stata avviata una nuova transazione ma manca il COMMIT/ROLLBACK corrispondente.

Per quello che ho scoperto, ha qualcosa a che fare con due istanze SQLAlchemy che si creano anche, una volta nel file del modello e una volta nel web.py. Molto probabilmente è perché si interagisce con la sessione di web.py e se si interrogano i modelli è presente un contesto che riceverà il COMMIT.

Ho risolto il problema importando "db" dai modelli e avvialo chiamando db.init_app (app). Secondo i registri, il commit ora funziona finde.

Il @app.teardown_appcontext non dovrebbe essere necessario in quanto si trova in classe SQLAlchemy di Flask-SQLAlchemy (https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy/init.py)