SQLAlchemy中Relationship的validates赋值时不触发,如何解决?
解决方案:让直接赋值触发验证或限制修改方式
一、让直接赋值也触发验证
@validates('failure_modes')仅监听集合内元素的增减操作(比如append),直接赋值整个列表属于替换属性值,不会触发该验证。要覆盖这种场景,需使用SQLAlchemy的属性事件监听set动作:
from sqlalchemy.orm import validates, Mapped, mapped_column, relationship from sqlalchemy import event from datetime import datetime from typing import List class DeviceTestResult: __tablename__ = "device_test" passed: bool = mapped_column(default=False, init=False) failure_modes: Mapped[List['FailureMode']] = relationship(default_factory=list, back_populates='test') @validates('failure_modes') def validate_failure_modes(self, key, failure_mode): """验证单个failure_mode添加时的状态""" self.passed = all(fm.passed for fm in self.failure_modes + [failure_mode]) return failure_mode class FailureMode: __tablename__ = "failure_mode" passed: bool = mapped_column() test: Mapped['DeviceTestResult'] = relationship(back_populates='failure_modes', default=None) completed_at: datetime = mapped_column(insert_default=datetime.now(), default=datetime.now()) @validates('passed') def validate_test(self, key, passed): if self.test: self.test.passed = all(fm.passed for fm in self.test.failure_modes) return passed # 监听failure_modes的赋值事件 @event.listens_for(DeviceTestResult.failure_modes, 'set') def on_failure_modes_set(target, new_value, old_value, initiator): # 重新计算passed状态 target.passed = all(fm.passed for fm in new_value) return new_value
说明:
event.listens_for会在failure_modes被直接赋值时触发,这里直接基于新列表的所有元素重新计算passed状态,逻辑更简洁。- 原有的
validate_failure_modes仍处理元素增减的场景,两者配合覆盖所有修改方式。
二、禁止直接赋值,仅允许append/extend修改
如果要强制用户只能通过append/extend等方法修改集合,可以自定义受限列表类,替换默认的集合类型:
class RestrictedList(list): def __setitem__(self, key, value): """禁止通过索引赋值""" raise AttributeError("Cannot assign via index. Use append/extend instead.") def __setattr__(self, name, value): """禁止直接修改属性(避免绕过限制)""" if name not in ('__dict__', '__len__'): raise AttributeError("Cannot assign directly to this list. Use append/extend instead.") super().__setattr__(name, value) class DeviceTestResult: __tablename__ = "device_test" passed: bool = mapped_column(default=False, init=False) failure_modes: Mapped[List['FailureMode']] = relationship( default_factory=RestrictedList, back_populates='test', collection_class=RestrictedList ) # 其余代码保持不变...
说明:
default_factory确保初始化时创建RestrictedList实例,collection_class指定SQLAlchemy加载关联数据时使用该类。- 当用户尝试
device.failure_modes = [failure_mode]或device.failure_modes[0] = xxx时,会抛出AttributeError,强制使用集合的添加方法。
内容的提问来源于stack exchange,提问作者frimann
相关产品推荐
相关产品推荐

