Pythonic способ правильно отделить модель от приложения с помощью SQLAlchemy

Мне трудно запустить мое приложение. Расширение Flask-SQLAlchemy создает пустую базу данных всякий раз, когда я пытаюсь разделить модуль на пакеты. Чтобы лучше объяснить, что я делаю, позвольте мне показать, как устроен мой проект:

Project
|
|-- Model
|   |-- __init__.py
|   |-- User.py
|
|-- Server
|   |-- __init__.py
|
|-- API
|   |-- __init__.py

Идея проста: хочу создать пакет для своей модели, так как не люблю выкладывать код в один пакет, и отдельные "под" проекты (типа API), так как в будущем буду использовать блюпринты для лучшего изолировать вспомогательные приложения.

Код очень прост:

Во-первых, Model.__init__.py:

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

Обратите внимание, что я создал это только для использования одного объекта SQLAlchemy() в пакете. Нет, мы идем к Model.User

from Model import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    Name = db.Column(db.String(80))
    Age = db.Column(db.Integer)
    ...

Еще раз обратите внимание на базу данных импорта из модели, которую я использовал для разрешения того же объекта базы данных.

Наконец, Server.__init__.py выглядит так:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import Model, API
db = Model.db


def main():
    app = Flask("__main__")
    db = SQLAlchemy(app)
    db.create_all()
    API.SetAPIHookers(app)
    app.run(host="0.0.0.0", port=5000, debug=True)

if __name__ == "__main__":
    main()

С моей точки зрения, db = SQLAlchemy(app) позволяет мне передавать объект моего приложения без создания циклической ссылки.

Проблема в том, что всякий раз, когда я запускаю этот код, файл базы данных sqlite создается пустым. Это заставило меня подумать, что, возможно, Python не импортирует вещи, как я думал. Итак, я проверил свою теорию, удалив модель импорта и создав пользователя непосредственно внутри сервера... и вуаля, это сработало!

Теперь возникает мой вопрос: есть ли «питоновский» способ правильно разделить модули, как я хочу, или я должен оставить все в одном пакете?


person Rafa Borges    schedule 25.09.2013    source источник
comment
Много лет спустя предложение Python не импортирует вещи, как я думал, все еще проблема. Кажется, что все еще существует ОГРОМНЫЙ разрыв между тем, как Python преподается в блогах, видео и даже на курсах, и Python в реальной рабочей среде. Мой совет новичкам: не ограничивайтесь простыми примерами, которые показывают только основные функции; всегда делайте шаг вперед и проверяйте, как они используются в реальном мире. Но всегда проверяйте баланс между вознаграждением и затратами.   -  person sflr    schedule 20.01.2021


Ответы (2)


Прямо сейчас вы настроили свое приложение, используя то, что является грубым эквивалентом "Application Factory" (так называемый в документации Flask). Это идея Flask, а не Python. У него есть некоторые преимущества, но это также означает, что вам нужно делать такие вещи, как инициализация вашего объекта SQLAlchemy, используя метод init_app, а не конструктор SQLAlchemy. Нет ничего «плохого» в том, чтобы сделать это таким образом, но это означает, что вам нужно запускать такие методы, как create_all(), внутри контекст приложения, которым в настоящее время вы бы не были, если бы попытались запустить его в методе main().

Есть несколько способов решить эту проблему, но вам решать, какой из них вы хотите (правильного ответа нет):

Не используйте шаблон фабрики приложений

Таким образом, вы не создаете приложение в функции. Вместо этого вы помещаете его куда-нибудь (например, в project/__init__.py). Ваш файл project/__init__.py может импортировать пакет models, а пакет models может импортировать app из project. Это циклическая ссылка, но это нормально, если объект app создается в пакете project до того, как model попытается импортировать app из package. См. документацию Flask в разделе Большие шаблоны приложений, чтобы увидеть пример, в котором вы можете разделить свой пакет на несколько пакетов, но при этом другие пакеты могут использовать объект app с помощью циклических ссылок. В документах даже говорится:

Каждый программист на Python их ненавидит, но мы только что добавили кое-что: циклический импорт. [...] Имейте в виду, что в целом это плохая идея, но здесь все в порядке.

Если вы сделаете это, вы можете изменить свой файл Models/__init__.py, чтобы создать объект SQLAlchemy со ссылкой на приложение в конструкторе. Таким образом, вы можете использовать методы create_all() и drop_all() объекта SQLAlchemy, , как описано в документации. для Flask-SQLAlchemy.

Сохраните то, что у вас есть сейчас, но создайте request_context()

Если вы продолжите с тем, что у вас есть сейчас (создание вашего приложения в функции), вам нужно будет создать объект SQLAlchemy в пакете Models без использования объекта app как части конструктора (как вы сделали). В вашем основном методе измените...

db = SQLAlchemy(app)

...to a...

db.init_app(app)

Затем вам нужно будет переместить метод create_all() в функцию внутри контекста приложения. Обычный способ сделать это для чего-то на таком раннем этапе проекта — использовать before_first_request() декоратор....

app = Flask(...)

@app.before_first_request
def initialize_database():
    db.create_all()

Метод «initialize_database» запускается до того, как Flask обработает первый запрос. Вы также можете сделать это в любой момент, используя метод app_context():

app = Flask(...)
with app.app_context():
    # This should work because we are in an app context.
    db.create_all()

Поймите, что если вы собираетесь продолжать использовать шаблон «Фабрика приложений», вы должны действительно понимать, как работает контекст приложения; поначалу это может сбить с толку, но необходимо понять, что означают такие ошибки, как «приложение, не зарегистрированное в экземпляре БД, и приложение не привязано к текущему контексту».

person Mark Hildreth    schedule 25.09.2013
comment
На самом деле не будет. db = SQLAlchemy(app) — это мой способ сбросить переменную db с правильным контекстом приложения. Если я удалю эту строку, я получу следующую ошибку: RuntimeError: приложение не зарегистрировано в экземпляре БД и приложение не привязано к текущему контексту. - person Rafa Borges; 25.09.2013
comment
Насчет заглавных, вы правы! Я исправлю это. Мой опыт C++/C# слишком силен во мне. - person Rafa Borges; 25.09.2013
comment
Неудачно. Приложение по-прежнему не зарегистрировано в экземпляре БД и приложение не привязано к текущей ошибке контекста. Мне кажется, что после создания экземпляра переменной db новый контекст приложения не ограничен. - person Rafa Borges; 25.09.2013
comment
Удивительный ответ! Прочитав все, что вы написали и связали, я смог понять, что мне нужно было только простое приложение с app.test_request_context(): перед вызовом моей Model.db.create_all(). Также я переписал его, чтобы он лучше подходил для сценария с несколькими приложениями. Спасибо!! - person Rafa Borges; 25.09.2013
comment
После часа гугления, наконец, ОТВЕТ! - person Private; 12.10.2015
comment
вы также можете сделать это db.create_all(app=app) и установить аргумент app для вашего текущего приложения - person danidee; 20.07.2016

Ваша проблема в этой строке:

db = SQLAlchemy(app)

должно быть так:

db.init_app(app)

Запустив приложение SQLAlchemy снова, вы переназначаете db вновь созданному объекту db.

Пожалуйста, постарайтесь НЕ отходить от заводской настройки приложения. Это устраняет побочные эффекты времени импорта и является ХОРОШЕЙ вещью. На самом деле вы можете захотеть импортировать db внутри вашей фабрики, потому что импорт модели, которая является подклассом Base (в данном случае db.model), имеет свои собственные побочные эффекты (хотя и менее проблематичные).

Инициализация вашего приложения в __init__.py означает, что когда вы импортируете что-либо из своего пакета для использования, вы в конечном итоге загрузите свое приложение, даже если оно вам не нужно.

person Andrew Kou    schedule 23.06.2014