2015-10-14 13 views
12

Mam uruchomioną aplikację Flask, która została skonfigurowana zgodnie z najlepszą praktyką znalezioną online oraz w książce "Flask Web Development" autorstwa Miguela Grinberga.Udostępnianie modeli sqlalchemy między kolbą a innymi aplikacjami

Potrzebujemy teraz drugiej aplikacji Pythona, która NIE jest aplikacją internetową i wymaga dostępu do tych samych modeli co aplikacja Flask. Chcieliśmy ponownie wykorzystać te same modele, więc obie aplikacje mogą korzystać ze wspólnego kodu.

Usunęliśmy zależności na rozszerzeniu kolby-sqlalchemy (którego używaliśmy wcześniej, kiedy mieliśmy tylko aplikację Flask). Zastąpiono go numerem SQLalchemy Declarative extension described here, który jest nieco prostszy (Flask-SQLalchemy adds a few specific things to standard SQLAlchemy). W naszym przypadku są dwie rzeczy różne od Deklaratywnego przykładu rozszerzenia: umieszczam silnik i sesję w klasie, ponieważ wszystkie nasze modele używają db.session, zamiast db_session, i przekazuję słownik z wartościami konfiguracji do init(), dzięki czemu mogę ponownie użyć tego pliku database.py zarówno z Flask, jak i innej aplikacji, używając innej konfiguracji. to wygląda tak:

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

Teraz dochodzimy do rzeczywistego problemu. Flask tworzy obiekt słownikowy zawierający opcje konfiguracji i dodaje je jako właściwość do instancji aplikacji. Ładuje je z katalogu instance folder, config.py w katalogu głównym witryny i ze zmiennych środowiskowych. Muszę przekazać w słowniku konfiguracji z Flask, więc potrzebuję Flask do PIERWSZEGO załadowania i złożenia konfiguracji, a następnie zainicjować bazę danych i mieć (skonfigurowany) obiekt db w katalogu głównym pliku aplikacji. Jednak podążamy za Application factory pattern, dzięki czemu możemy korzystać z różnych konfiguracji dla różnych sytuacji (test, produkcja, rozwój).

Oznacza to nasza app/__init__.py wygląda mniej więcej tak (uproszczony):

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 

Ale dB (że modele importować z ..), teraz musi być wewnątrz funkcji create_app(), bo tam Kolba ładuje konfigurację. Jeśli chciałbym utworzyć instancję obiektu db poza funkcją create_app(), będzie można ją importować z modeli, ale nie jest ona skonfigurowana!

przykładem modelu wygląda to i tak widać, że spodziewa się „db” w katalogu głównym aplikacji:

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

Więc moje pytanie brzmi, jak mogę mieć wystąpienie db że mogą być konfigurowane zewnętrznie (przez Flask lub inną aplikację) i nadal używać Wzorca Fabrycznego Aplikacji?

edit: Kod-przykładem była błędna, to miał import dla skrzynkowego sqlalchemy który został zastąpiony przez from database import Database. Przepraszamy za jakiekolwiek zamieszanie.

+0

Funkcja porzuca który wywołuje 'db.session.remove' jest niepotrzebne i może rzeczywiście powodować problemy. – davidism

+1

Funkcja ta jest niedostępna przy użyciu metody Deklaratywnej, zgodnie z dokumentacją Flask: http://flask.pocoo.org/docs/0.10/patterns/sqlalchemy/ –

+1

Ponieważ jest to sesja z zakresem, jest to po prostu zbędne. Dziękuję za wskazanie tego, ale muszę naprawić te dokumenty. – davidism

Odpowiedz

12

Rozszerzenie Flask-SQLAlchemy, podobnie jak większość rozszerzeń kolb, powinno zostać utworzone poza fabryką, a następnie zainicjowane w fabryce przy użyciu init_app. Jest tak, aby móc użyć obiektu db przed utworzeniem aplikacji.

Twoja aplikacja Flask, podobnie jak każdy prawidłowo zaprojektowany projekt w języku Python, powinna być instalowalną paczką. Jest to proste: upewnij się, że układ projektu ma sens, a następnie dodaj podstawowy plik 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 

Teraz można zainstalować aplikację kolby wraz z jego bazy danych, do wykorzystania w innych projektach. Zainstaluj i zaimportuj go do virtualenv drugiego projektu, a następnie utwórz aplikację i uruchom ją, aby ją zainicjować.

$ 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

Dziękuję @davidism, ale to tworzy pełny kontekst kolby wewnątrz mojego projektu dodatkowego. Drugorzędny projekt to wykonywalny proces w tle. Wygląda na to, że posiadanie kompletnej instancji/kontekstu w kolbie tylko po to, aby dzielić te same modele baz danych, jest nieco przesadzone. Chciałbym oddzielić tylko modele, bez uruchamiania ich pod Flask. –

+1

Nie zgadzam się. Kontekst Flask nie jest bardzo skomplikowany i radzisz sobie z nim dokładnie raz. W rzeczywistości nie używasz Flask, tylko używasz jego konfiguracji. Prowadzę szereg zadań w tle z Selerami, wszystko w kontekście mojej aplikacji i nigdy nie miałem problemów. Zastanów się też, co jeśli chcesz zrobić coś w rodzaju wysyłania wiadomości e-mail po wykonaniu zadania? Lub generować hasła za pomocą Bcrypt? Głupotą byłoby ponowne odkrywanie koła, gdy jest już zdefiniowane i skonfigurowane. – davidism

+2

hhhmm .. Rozumiem twój punkt, ale druga strona opowieści brzmi: co, jeśli * nie * muszę wysyłać e-maili lub używać Bcrypt (co jest bardziej prawdopodobne)? W takim przypadku istnieje garść kodu w mojej bazie kodów, która nigdy nie była używana ani aktualizowana. Lub jeśli potrzebuję nowszej wersji biblioteki Bcrypt w Flask, ale jest to niezgodne z moim projektem pomocniczym. Wolałbym mieć osobne projekty, które używają/importują dokładnie to, czego potrzebują, aby zapobiec takim sytuacjom. –

2

Dla innych ludzi wyruszą w tym kierunku. There is quite a good blog post i link to a library, który oferuje zalety Flask-SQLAlchemy, bez bezpośredniego łączenia SQLAlchemy z Flask.

Słowo ostrzeżenia jednak; Próbowałem użyć Alchy, ale wciąż nie mogłem zrozumieć, jak zintegrować go z Flask i aplikacją inną niż webowa, więc poszedłem z zaakceptowaną odpowiedzią przez davidism na to pytanie. Twój przebieg może się różnić.

+1

@dgilland może być w stanie udzielić odpowiedzi na temat integracji Alchy z Flask i aplikacją inną niż web –

1

Wpadłem na ten sam problem.

Jeśli włączysz "SQLALCHEMYYECH", najprawdopodobniej zobaczysz, że nowa transakcja została uruchomiona, ale brakuje odpowiedniego COMMIT/ROLLBACK.

Co odkryłem, ma to coś wspólnego z dwoma instancjami SQLAlchemy, które również tworzysz, raz w pliku modelu i raz w web.py. Najprawdopodobniej dzieje się tak, ponieważ wchodzisz w interakcję z sesją web.py. Jeśli zapytasz swoje modele, istnieje kontekst, w którym otrzymasz COMMIT.

Naprawiono problem przez zaimportowanie "db" z modeli, a następnie zainicjowanie go przez wywołanie db.init_app (app). Zgodnie z dziennikami, zobowiązując teraz działa finde.

@app.teardown_appcontext nie powinno być konieczne, ponieważ jest on ustawiony w skrzynkowego sqlalchemy za sqlalchemy klasy (https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy/init.py)

Powiązane problemy