使用Blueprint中的元数据创建Flask-SQLAlchemy实例

时间:2019-04-21 11:56:46

标签: python flask sqlalchemy flask-sqlalchemy python-import

TL; DR:如何使用蓝图中的metadata对象创建Flask-SQLAlchemy实例?我看到的唯一提供声明性基础metadata对象的地方是在初始SQLAlchemy()调用中。但是,当我从自己的extensions.py文件中的蓝图导入它时,蓝图的代码需要db对象,并且由于循环导入而导致加载失败。


我想在Flask的内部和外部使用几个模型类。我正在使用the declarative method进行此操作,并且我的应用程序设置为使用App Factory模型和蓝图。在创建metadata对象时,通过使用db参数向SQLAlchemy注册模型的方法。在我的应用程序上下文中,有必要在蓝图中而不是在主应用蓝图中声明metadata对象。 (这是大多数引用它的代码所在的位置,包括用于最初填充数据库的非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)

this general question on circular imports中关于蓝图所述,一种可行的解决方案是从第二个蓝图中的每个函数内部导入db对象,从而在{{1 }}文件。但是除了烦人之外,这感觉还很骇人。

理想情况下,我可以将我创建的extensions.py对象传递给SQLAlchemy的metadata方法。那将很快解决这个问题。不幸的是,init_app()没有接受init_app()参数。初始化后,还有其他方法可以向SQLAlchemy实例注册元数据吗?还是我错过了声明式模型方法的其他关键要素?

我应该说,非Flask部分工作正常。我的实用程序脚本能够导入模型,并使用它们将对象添加到数据库。只是Flask的进口给我带来了麻烦。

这是层次结构:

metadata

以及由于循环导入而失败的相关代码:

app / __ init __。py:

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

app / extensions.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)

草皮/ __ init __。py:

from turf.models import metadata

db = SQLAlchemy(metadata=metadata)

草皮/模型.py:

from .routes import bp

草皮/routes.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...

1 个答案:

答案 0 :(得分:0)

事实证明,您可以在extensions.py文件中声明MetaData对象,然后将其导入到蓝图中。我确信这会失败,因为在创建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)

草皮/模型.py:

from app.base import Base

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

这也回答了我最初的方法所遇到的另一个问题:如果我有第二个蓝图,并且该蓝图也具有需要从非Flask代码中获得的模型对象,它将如何工作?现在,我已经创建了一个基础对象,可以根据需要使用该对象在不同的​​蓝图中实现新类。

不过,这种方法有点烦人。在非Flask DB填充脚本中,我最初能够使用from models import *来引用包含模型的兄弟模块(文件)。这让我直接调用脚本,例如cd turf; python populate_db.py --arg。这不再起作用,因为models.py文件现在引用了另一个包app.extensions。因此,我不得不使用以下解决方法:

草皮/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)