web-dev-qa-db-fra.com

Partagez des modèles sqlalchemy entre le flacon et d'autres applications

J'ai une application Flask en cours d'exécution configurée selon une combinaison des meilleures pratiques que nous avons trouvées en ligne et dans le livre "Flask Web Development" de Miguel Grinberg.

Nous avons maintenant besoin d'une deuxième application Python, qui n'est PAS une application Web et qui doit avoir accès aux mêmes modèles que l'application Flask. Nous voulions réutiliser les mêmes modèles de cours afin que les deux applications puissent tirer parti du code partagé.

Nous avons supprimé les dépendances sur l'extension flask-sqlalchemy (que nous utilisions auparavant, lorsqu'il ne s'agissait que de l'application Flask). Et remplacé par extension déclarative SQLalchemy décrite ici }, qui est un peu plus simple ( Flask-SQLalchemy ajoute quelques éléments spécifiques à SQLAlchemy standard )

Conformément à l'exemple, nous avons créé un fichier database.py à la racine. Dans notre cas, il y a deux choses différentes de l'exemple d'extension déclarative: je place le moteur et la session dans une classe, car tous nos modèles utilisent db.session, au lieu de db_session, et je passe un dictionnaire avec des valeurs de configuration au init () , afin que je puisse réutiliser ce fichier database.py à la fois par Flask et par une autre application, en utilisant une configuration différente. ça ressemble à ça:

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

Nous en arrivons maintenant au problème actuel. Flask crée un objet de type dictionnaire contenant des options de configuration et les ajoute en tant que propriété à l'instance d'application. Il les charge à partir d'un dossier d'instance , d'un config.py à la racine du site et de variables d'environnement. J'ai besoin de transmettre le dictionnaire de configuration à partir de Flask. J'ai donc besoin que Flask charge et assemble la configuration, puis initialise la base de données et dispose d'un objet de base de données (configuré) à la racine du fichier d'application. Cependant, nous suivons le modèle de fabrique d'applications afin de pouvoir utiliser différentes configurations pour différentes situations (test, production, développement). 

Cela signifie que notre app/__init__.py ressemble à ceci (simplifié):

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

Mais la base de données (importée par les modèles depuis ..) doit maintenant se trouver dans la fonction create_app (), car c'est là que Flask charge la configuration. Si j'instanciais l'objet db en dehors de la fonction create_app (), il serait importable à partir des modèles, mais ce n'est pas configuré!

un exemple de modèle ressemble à ceci et, comme vous pouvez le constater, il attend un "db" à la racine de l'application:

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

Ma question est donc la suivante: comment puis-je avoir une instance de base de données pouvant être configurée en externe (par Flask ou une autre application), tout en utilisant le modèle Application Factory? 

edit: L'exemple de code était incorrect, il s'agissait d'une importation pour Flask-SQLalchemy qui a été remplacée par from database import Database. Désolé pour toute confusion.

19
Erik Oosterwaal

L'extension Flask-SQLAlchemy, comme la plupart des extensions Flask, doit être créée en dehors de la fabrique, puis initialisée en usine à l'aide de init_app. Cela vous permet d'utiliser l'objet db avant la création d'une application.

db = SQLAlchemy()

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

Votre application Flask, comme tout projet Python correctement conçu, devrait constituer un package installable. C'est simple: assurez-vous que la structure de votre projet a du sens, puis ajoutez un fichier setup.py de base.

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

Vous pouvez maintenant installer votre application Flask, ainsi que sa base de données, pour les utiliser dans d'autres projets. Installez-la et importez-la dans virtualenv de votre deuxième projet, puis créez et poussez une application pour l'initialiser.

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

Si vous êtes préoccupé par les coûts liés à la création de votre application, vous pouvez ajouter des arguments à la fonction create_app pour contrôler les éléments à initialiser. Dans la plupart des cas, cela ne devrait cependant pas être un problème.

19
davidism

J'ai rencontré le même problème.

Si vous activez "SQLALCHEMY_ECHO", vous constaterez probablement qu'une nouvelle transaction est lancée mais que la commande COMMIT/ROLLBACK correspondante est manquante.

Pour ce que j'ai découvert, cela a quelque chose à voir avec deux instances SQLAlchemy que vous créez également, une fois dans votre fichier de modèle et une fois dans votre web.py. C'est probablement parce que vous interagissez avec la session de votre web.py et si vous interrogez vos modèles, il y a un changement de contexte qui recevra le COMMIT.

J'ai résolu le problème en important "db" à partir de modèles, puis en l'initialisant en appelant db.init_app (app). Selon les journaux, s’engager fonctionne maintenant très bien.

Le @app.teardown_appcontext ne devrait pas être nécessaire car il est configuré dans la classe SQLAlchemy de Flask-SQLAlchemy ( https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy/ init . Py. )

2
stffn

Pour d'autres personnes qui s'aventurent dans cette direction. Il existe un bon article de blog et un lien vers une bibliothèque qui offre des avantages similaires à Flask-SQLAlchemy, sans lier directement SQLAlchemy à Flask. 

Un mot d'avertissement cependant; J'ai essayé d'utiliser Alchy, mais je n'arrivais toujours pas à comprendre comment l'intégrer à la fois dans Flask et dans une application non-Web, alors je suis allé avec la réponse acceptée par davidism à cette question. Votre kilométrage peut varier.

2
Erik Oosterwaal

Vous pouvez facilement partager. Je vais montrer comment. Considérant cette application Flask:

.
├── 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)

D'ACCORD. Maintenant, vous voulez utiliser les mêmes modèles pour faire autre chose. Par exemple: former une machine, servir et enregistrer dans une base de données (ORM) en utilisant les mêmes modèles.

Vous pouvez importer l'application et utiliser la fonction app.test_request_context (). Comme ça:

do_something.py

depuis l'application d'importation web.api.app à partir de la base de données d'importation web.api.models, utilisateur

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 (exemple réel)

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!')

Plusieurs réponses recommandent d’utiliser (Importer des fichiers depuis différents dossiers):

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

Mais je ne suis pas d'accord lorsque vous avez une application Flask et d'autres scripts, car vous deviendrez fou en résolvant les références relatives. 

Une autre option consiste à installer votre application Flask, ainsi que sa base de données, pour une utilisation dans d'autres projets.

Ici vous pouvez trouver une documentation sur les packages et modules .

Les packages sont un moyen de structurer l’espace de noms de modules de Python en utilisant “Noms de modules en pointillés”. Par exemple, le nom de module AB désigne un sous-module Nommé B dans un package nommé A. Tout comme l'utilisation de modules Évite aux auteurs de modules différents de s'inquiéter de chaque [.____. Les noms de variables globales de other, l'utilisation de noms de modules en pointillés évite à les auteurs de packages multi-modules tels que NumPy ou Pillow d'avoir pour s'inquiéter des noms de modules respectifs.

0
Andre Araujo