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

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

Наконец, 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 не импортирует вещи, как я думал. Поэтому я протестировал свою теорию, удалив импортирующую модель и создав пользователя непосредственно внутри сервера... и он работает!

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

Ответ 1

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

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

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

Таким образом, вы не создаете приложение в функции. Вместо этого вы помещаете его где-нибудь (например, в 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)

... до...

db.init_app(app)

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

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

Поймите, что если вы собираетесь продолжать использовать шаблон Application Factory, вы должны действительно понять, как работает контекст приложения; это может сбивать с толку сначала, но необходимо понять, какие ошибки, такие как "приложение, не зарегистрированное на экземпляре db и не связанное с текущим контекстом", означают.

Ответ 2

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

db = SQLAlchemy(app)

он должен быть следующим:

db.init_app(app)

Снова запустив приложение SQLAlchemy, вы переназначаете db для вновь созданного db obj.

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

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