如何在 Flask/SQLAlchemy 模型中防止这种循环引用?

How to prevent this circular reference in Flask/SQLAlchemy Models?

我的 Flask/SQLAlchemy 应用程序中有以下四个模型 classes(第五个 class MyClassA 未在此处显示,但供参考):

class MyClassF(db.Model):
    valid_fs = ['F1', 'F2']
    f_id = db.Column(db.Integer, nullable=False, primary_key=True)
    name = db.Column(db.Enum(*valid_fs, name='f_enum'), default='F1', nullable=False)

class MyClassD(db.Model):
    d_id = db.Column(db.Integer, nullable=False, primary_key=True)
    name = db.Column(db.String(128))
    v_collection = db.relationship('MyClassV', lazy='dynamic', backref=db.backref('d'), cascade='all, delete-orphan')

class MyClassV(db.Model):
    v_id = db.Column(db.Integer, nullable=False, primary_key=True)
    d_id = db.Column(db.Integer, db.ForeignKey(MyClassD.d_id), nullable=False)
    c_collection = db.relationship('MyClassC', lazy='dynamic', backref=db.backref('v'), cascade='all, delete-orphan')
    a_collection = db.relationship('MyClassA', lazy='dynamic', backref=db.backref('v'), cascade='all, delete-orphan')

class MyClassC(db.Model):
    c_id = db.Column(db.Integer, nullable=False, primary_key=True)
    v_id = db.Column(db.Integer, db.ForeignKey(MyClassV.v_id), nullable=False)
    f_id = db.Column(
        db.Integer,
        db.ForeignKey(MyClassF.f_id),
        nullable=False,
        #default=MyClassF.query.filter(MyClassF.name == "F1").one().f_id
    )

使用 Flask-Migrate 命令 db initdb migratedb upgrade 创建此架构效果很好。

然而,当我取消对 MyClassC.f_id 定义行的注释并重试时(删除 migrations 目录后),我得到以下循环依赖错误:

sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|MyClassV|my_classV, expression 'MyClassC' failed to locate a name ("name 'MyClassC' is not defined"). If this is a class name, consider adding this relationship() to the class after both dependent classes have been defined.

我要做的就是确保通过查询 MyClassF table 设置默认值 MyClassC.f_id。此检查应在插入时进行——而不是在创建数据库时进行。所以我不明白为什么我现在收到这个错误。

如何使用 db.relationship()(或任何其他技术)在强制执行我尝试实施的数据库完整性规则的同时解决此循环依赖错误?

我相信默认参数被翻译成SQL DEFAULT constraint。 AFAICT 在插入时不评估此约束,但在创建 table 时将该值用作默认值(这意味着默认值在创建 table 时一劳永逸地设置并使用对于缺少该列的所有行,它不能根据其他 table) 的内容动态更改。

但是 the documentation of SQLAlchemy 提到您可以传入一个 python 函数 作为默认值,每次插入都会调用它来获取要使用的默认值,所以你可以这样做:

class MyClassC(db.Model):
    c_id = db.Column(db.Integer, nullable=False, primary_key=True)
    v_id = db.Column(db.Integer, db.ForeignKey(MyClassV.v_id), nullable=False)
    f_id = db.Column(
        db.Integer,
        db.ForeignKey(MyClassF.f_id),
        nullable=False,
        default=lambda: MyClassF.query.filter(MyClassF.name == "F1").one().f_id
    )

但是请注意,这将不会使用任何数据库工具来提供默认值,它是 SQLAlchemy 首先获取默认值,然后手动将其插入到您的查询中。