使用 Blueprint 的元数据创建 Flask-SQLAlchemy 实例
Creating Flask-SQLAlchemy instance with metadata from Blueprint
TL;DR: 如何使用蓝图中的 metadata
对象来创建 Flask-SQLAlchemy 实例?我可以看到提供声明性基础 metadata
对象的唯一地方是在初始 SQLAlchemy()
调用中。但是当我从我的 extensions.py
文件中的蓝图导入它时,蓝图的代码需要 db
对象并且由于循环导入导致加载失败。
我有几个模型 classes,我想在 Flask 内外使用它们。我正在使用 来执行此操作,我的应用程序设置为使用应用程序工厂模型和蓝图。模型在 SQLAlchemy 中注册的方式是通过在创建 db
对象时使用 metadata
参数。在我的应用程序上下文中,在蓝图中而不是在主应用程序蓝图中声明 metadata
对象是有意义的。 (这是引用它的大部分代码所在的位置,包括用于初始填充数据库的非 Flask 实用程序脚本。)但是,从第二个蓝图导入模型 class 以循环导入结束。
$ 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 for Blueprints 中所述,一个有效的解决方案是从第二个蓝图中的每个函数内部导入 db
对象,从而在 db
初始化期间回避导入=20=] 文件。但除了烦人之外,这还让人觉得非常 hacky。
理想情况下,我可以将我创建的 metadata
对象传递给 SQLAlchemy 的 init_app()
方法。那将一下子解决这个问题。不幸的是,init_app()
不接受 metadata
参数。初始化后是否有其他方法可以将元数据注册到 SQLAlchemy 实例?还是我错过了声明性模型方法的其他一些关键要素?
我应该说它的非 Flask 部分工作得很好。我的实用程序脚本能够导入模型并使用它们将对象添加到数据库中。只有 Flask 导入给我带来了麻烦。
这是层次结构:
.
├── app
│ ├── __init__.py
│ └── extensions.py
└── turf
├── __init__.py
├── models.py
└── routes.py
以及循环导入失败的相关代码:
app/__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)
app/extensions.py:
from turf.models import metadata
db = SQLAlchemy(metadata=metadata)
turf/__init__.py:
from .routes import bp
turf/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...
turf/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')
事实证明,您可以在 extensions.py
文件中声明元数据对象,然后将其导入蓝图中。我确信这会失败,因为 metadata
对象现在在创建 db
对象后被填充,但我已经验证模型确实可用并且按预期工作。并且不再有循环依赖。实际上,我已将这部分分解成自己的文件,以允许尽可能少地导入蓝图代码。
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...
这也回答了我在最初的方法中遇到的另一个问题:如果我有第二个蓝图,它也有需要从非 Flask 代码中获得的模型对象,它会如何工作?现在,我已经创建了一个 Base 对象,并且可以使用它来根据需要在不同的蓝图中实现新的 类。
不过,这种方法有一个小问题。在非 Flask DB 填充脚本中,我最初可以使用 from models import *
来引用包含模型的兄弟模块(文件)。这让我可以直接调用脚本,à la 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)
TL;DR: 如何使用蓝图中的 metadata
对象来创建 Flask-SQLAlchemy 实例?我可以看到提供声明性基础 metadata
对象的唯一地方是在初始 SQLAlchemy()
调用中。但是当我从我的 extensions.py
文件中的蓝图导入它时,蓝图的代码需要 db
对象并且由于循环导入导致加载失败。
我有几个模型 classes,我想在 Flask 内外使用它们。我正在使用 db
对象时使用 metadata
参数。在我的应用程序上下文中,在蓝图中而不是在主应用程序蓝图中声明 metadata
对象是有意义的。 (这是引用它的大部分代码所在的位置,包括用于初始填充数据库的非 Flask 实用程序脚本。)但是,从第二个蓝图导入模型 class 以循环导入结束。
$ 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 for Blueprints 中所述,一个有效的解决方案是从第二个蓝图中的每个函数内部导入 db
对象,从而在 db
初始化期间回避导入=20=] 文件。但除了烦人之外,这还让人觉得非常 hacky。
理想情况下,我可以将我创建的 metadata
对象传递给 SQLAlchemy 的 init_app()
方法。那将一下子解决这个问题。不幸的是,init_app()
不接受 metadata
参数。初始化后是否有其他方法可以将元数据注册到 SQLAlchemy 实例?还是我错过了声明性模型方法的其他一些关键要素?
我应该说它的非 Flask 部分工作得很好。我的实用程序脚本能够导入模型并使用它们将对象添加到数据库中。只有 Flask 导入给我带来了麻烦。
这是层次结构:
.
├── app
│ ├── __init__.py
│ └── extensions.py
└── turf
├── __init__.py
├── models.py
└── routes.py
以及循环导入失败的相关代码:
app/__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)
app/extensions.py:
from turf.models import metadata
db = SQLAlchemy(metadata=metadata)
turf/__init__.py:
from .routes import bp
turf/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...
turf/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')
事实证明,您可以在 extensions.py
文件中声明元数据对象,然后将其导入蓝图中。我确信这会失败,因为 metadata
对象现在在创建 db
对象后被填充,但我已经验证模型确实可用并且按预期工作。并且不再有循环依赖。实际上,我已将这部分分解成自己的文件,以允许尽可能少地导入蓝图代码。
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...
这也回答了我在最初的方法中遇到的另一个问题:如果我有第二个蓝图,它也有需要从非 Flask 代码中获得的模型对象,它会如何工作?现在,我已经创建了一个 Base 对象,并且可以使用它来根据需要在不同的蓝图中实现新的 类。
不过,这种方法有一个小问题。在非 Flask DB 填充脚本中,我最初可以使用 from models import *
来引用包含模型的兄弟模块(文件)。这让我可以直接调用脚本,à la 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)