Создание экземпляра Flask-SQLAlchemy с метаданными из Blueprint - PullRequest
0 голосов
/ 21 апреля 2019

TL; DR: Как использовать объект metadata из Blueprint для создания экземпляра Flask-SQLAlchemy? Единственное место, где я могу предоставить декларативный базовый объект metadata, - это начальный вызов SQLAlchemy(). Но когда я импортирую его из Blueprint в моем файле extensions.py, для кода Blueprint требуется объект db, и загрузка не удалась из-за циклического импорта.


У меня есть несколько классов моделей, которые я хотел бы использовать как внутри, так и вне Flask. Для этого я использую декларативный метод , и мое приложение настроено на использование модели App Factory и Blueprints. Регистрация моделей в SQLAlchemy осуществляется с помощью аргумента metadata при создании объекта db. В контексте моего приложения имеет смысл объявить объект metadata в Blueprint, а не в основном приложении Blueprint. (Это - то, где большая часть кода, который ссылается на это, включая сценарий утилиты не Flask, используемый для начального заполнения базы данных.) Однако импорт класса модели из второго Blueprint заканчивается циклическим импортом.

$ flask db migrate
Error: While importing "my_app", an ImportError was raised:

Traceback (most recent call last):
  File "my_app/venv/lib/python3.7/site-packages/flask/cli.py", line 235, in locate_app
    __import__(module_name)
  File "my_app/my_app.py", line 1, in <module>
    from app import create_app
  File "my_app/app/__init__.py", line 7, in <module>
    from app.extensions import *
  File "my_app/app/extensions.py", line 10, in <module>
    from turf.models import metadata
  File "my_app/turf/__init__.py", line 1, in <module>
    from .routes import bp
  File "my_app/turf/routes.py", line 14, in <module>
    from app.extensions import db
ImportError: cannot import name 'db' from 'app.extensions' (my_app/app/extensions.py)

Как уже упоминалось в этом общем вопросе о циклическом импорте для Blueprints, решение, которое работает, заключается в том, чтобы импортировать объект db изнутри каждой функции во втором Blueprint, таким образом обойдя импорт при инициализации файла extensions.py. Но кроме того, что надоедает, это просто невероятно.

В идеале я бы мог передать созданный мною объект metadata методу init_app() SQLAlchemy. Это решило бы эту проблему одним махом. К сожалению, init_app() не принимает аргумент metadata. Есть ли другой способ регистрации метаданных с экземпляром SQLAlchemy после инициализации? Или я пропустил какой-то другой ключевой элемент подхода декларативной модели?

Я должен сказать, что часть, не относящаяся к колбе, работает просто отлично. Мой служебный скрипт может импортировать модели и использовать их для добавления объектов в базу данных. Только импорт фляги доставляет мне неприятности.

Вот иерархия:

.
├── app
│   ├── __init__.py
│   └── extensions.py
└── turf
    ├── __init__.py
    ├── models.py
    └── routes.py

И соответствующий код, который не работает из-за циклического импорта:

приложение / __ init__.py:

from app.extensions import *

def create_app():
    app = Flask(__name__)

    with app.app_context():
        import turf
        app.register_blueprint(turf.bp)

        db.init_app(app)

приложение / extensions.py:

from turf.models import metadata

db = SQLAlchemy(metadata=metadata)

дерн / __ init__.py:

from .routes import bp

дерн / models.py:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import MetaData

metadata = MetaData()
Base = declarative_base(metadata=metadata)

# All the turf models are declared in this file
class Boundary(Base):
    # ...etc...

дерн / routes.py:

from .models import *
from app.extensions import db

bp = Blueprint('Turf', __name__, url_prefix='/turf')

@bp.route('/')
def index():
    return render_template('turf/index.html')

1 Ответ

0 голосов
/ 21 апреля 2019

Оказывается, вы можете объявить объект MetaData в файле extensions.py, а затем импортировать его в Blueprint.Я был уверен, что это не удастся, потому что объект metadata теперь заполняется после создания объекта db, но я убедился, что модели действительно доступны и работают, как и ожидалось.И нет больше круговой зависимости.Я фактически разбил эту часть на собственный файл, чтобы позволить коду Blueprint импортировать как можно меньше.

app / base.py:

from sqlalchemy import MetaData
from sqlalchemy.ext.declarative import declarative_base

metadata = MetaData()
Base = declarative_base(metadata=metadata)

app / extensions.py:

from flask_sqlalchemy import SQLAlchemy
from .base import metadata

db = SQLAlchemy(metadata=metadata)

turf / models.py:

from app.base import Base

# All the turf models are declared in this file
class Boundary(Base):
    # ...etc...

Это также отвечает на другой мой вопросмой оригинальный подход: как бы это работало, если бы у меня был второй Blueprint, который также имел объекты модели, которые должны были быть доступны из не-Flask кода?Теперь я создал один базовый объект и могу использовать его для реализации новых классов в различных чертежах по мере необходимости.

Однако при этом подходе возникает одно небольшое неудобство.В сценарии заполнения базы данных, не относящегося к Flask, я изначально мог использовать from models import * для ссылки на родственный модуль (файл), содержащий модели.Это позволило мне вызвать скрипт напрямую, а-ля cd turf; python populate_db.py --arg.Это больше не работает, потому что файл models.py теперь ссылается на другой пакет, app.extensions.Поэтому вместо этого я должен использовать этот обходной путь:

turf / populate_db.py:

try:
    from .models import *
except:
    print("You have to run this from outside the 'turf' directory like so: $ python -m turf.populate_db [...]")
    sys.exit(1)
...