![](/img/trans.png)
[英]Flask-SQLAlchemy unittests failing InvalidRequestError: Table '{table_name}' is already defined for this MetaData instance
[英]Creating Flask-SQLAlchemy instance with metadata from Blueprint
TL; DR:如何使用Blueprint中的metadata
对象创建Flask-SQLAlchemy实例? 我可以看到提供声明性基本metadata
对象的唯一地方是在初始SQLAlchemy()
调用中。 但是当我从extensions.py
文件中的Blueprint导入它时,Blueprint的代码需要db
对象,并且由于循环导入而导致加载失败。
我有几个模型类,我想在Flask内外使用。 我使用声明方法来执行此操作,我的应用程序设置为使用App Factory模型和蓝图。 模型在SQLAlchemy中注册的方式是在创建db
对象时使用metadata
参数。 在我的应用程序的上下文中,在蓝图中而不是在主应用程序蓝图中声明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)
正如在关于蓝图的循环导入的一般问题中所提到的,一个有效的解决方案是从第二个蓝图中的每个函数内部导入db
对象,从而在extensions.py
文件初始化期间侧面执行导入。 但除了烦人之外,这只是感觉非常hacky。
理想情况下,我可以将我创建的metadata
对象传递给SQLAlchemy的init_app()
方法。 那将一下子解决这个问题。 不幸的是, init_app()
不接受metadata
参数。 是否有其他方法在初始化后使用SQLAlchemy实例注册元数据? 或者我是否错过了声明模型方法的其他关键元素?
我应该说,非Flask部分的工作正常。 我的实用程序脚本能够导入模型并使用它们将对象添加到数据库。 只有Flask进口才给我带来麻烦。
这是层次结构:
.
├── 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')
事实证明,您可以在extensions.py
文件中声明MetaData对象,然后将其导入Blueprint中。 我确信这会失败,因为现在正在创建db
对象之后填充metadata
对象,但我已经验证模型确实可用并且按预期工作。 并没有更多的循环依赖。 我实际上把这部分分解成了自己的文件,以便尽可能少地导入Blueprint代码。
应用程序/ base.py:
from sqlalchemy import MetaData
from sqlalchemy.ext.declarative import declarative_base
metadata = MetaData()
Base = declarative_base(metadata=metadata)
应用程序/ extensions.py:
from flask_sqlalchemy import SQLAlchemy
from .base import metadata
db = SQLAlchemy(metadata=metadata)
草坪/ 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 *
来引用包含模型的兄弟模块(文件)。 这让我直接调用脚本,àlacd cd turf; python populate_db.py --arg
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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.