考虑以下使用
sqlalchemy
:
from sqlalchemy.orm import validates
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):
"""
Validator for failure modes. When attribute is changed, updates passed to be false if there are any fails.
"""
currently_passes = all([fm.passed for fm in self.failure_modes])
self.passed = all([fm for fm in [currently_passes, failure_mode.passed]])
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):
"""
validator for passed. Updates associated test object status
"""
if self.test:
currently_passes = all([fm.passed for fm in self.test.failure_modes])
self.test._passed = all([fm for fm in [currently_passes, passed]])
return passed
# Case 1 - append, emits validate_failure_modes
device_test = DeviceTest()
failure_mode = FailureMode()
device.failure_modes.append(failure_mode)
# Case 2 - assignment, does not emit validate_failure_modes
device.failure_modes = [failure_mode]
validate_failure_modes
在情况1中发出,但在情况2中不发出。
如何在情况2中获得验证?如果这是不可能的,我如何限制用户使用赋值来设置属性,以便他们只能 通过附加进行修改? 我已经尝试过使用
描述符和混合,但还没有没让它发挥作用。
sqlalchemy
descriptors & hybrids but haven't gotten it to work.
你观察到的行为是因为 SQLAlchemy 处理列表类型关系上的赋值和附加操作的方式不同。当使用
append
时,SQLAlchemy 会检测到列表的变化并触发验证器。然而,当进行赋值时,它会将此解释为对整个列表的替换,因此不会为单个项目触发验证器。
以下是一些关于如何在这个场景中实现验证的方法:
1. 在
DeviceTestResult
上使用
event.listen_for
:
你可以使用
event.listen_for
在
DeviceTestResult
的
failure_modes
属性发生变化时触发一个函数。这将允许你在列表被修改时执行验证,而不管它是如何修改的。
from sqlalchemy import event
from sqlalchemy.orm import validates
class DeviceTestResult:
# ... other code ...
@validates('failure_modes')
def validate_failure_modes(self, key, failure_mode):
# ... your validation logic ...
@event.listen_for(DeviceTestResult.failure_modes, 'set')
def failure_modes_changed(target, value, oldvalue, initiator):
# This function will be called whenever the `failure_modes`
# attribute of a `DeviceTestResult` instance is modified.
# `target` is the `DeviceTestResult` instance
# `value` is the new list of `FailureMode` objects
# `oldvalue` is the old list of `FailureMode` objects
# Perform your validation logic here, similar to what you have in
# the `validate_failure_modes` function.
currently_passes = all([fm.passed for fm in value])
target.passed = currently_passes
2. 使用集合而不是列表:
另一种方法是将
failure_modes
关系表示为一个集合而不是一个列表。集合在 SQLAlchemy 中具有可变性跟踪功能,并且对集合的任何修改都会触发验证器。
from sqlalchemy.orm import relationship
class DeviceTestResult:
# ... other code ...
failure_modes: Mapped[set['FailureMode']] = relationship(
default_factory=set, back_populates='test'
)
@validates('failure_modes')
def validate_failure_modes(self, key, failure_mode):
# ... your validation logic ...
3. 创建一个自定义集合类:
你可以创建一个自定义集合类,该类继承自 Python 的
list
并覆盖
__setitem__
和
__delitem__
方法来触发验证逻辑。
class ValidatedList(list):
def __init__(self, parent, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parent = parent
def __setitem__(self, key, value):
super().__setitem__(key, value)
self.parent.validate_failure_modes('failure_modes', value)
def __delitem__(self, key):
super().__delitem__(key)
# Trigger validation after deletion if needed
class DeviceTestResult:
# ... other code ...
failure_modes: Mapped[list['FailureMode']] = relationship(
default_factory=lambda: ValidatedList(parent=self),
back_populates='test'
)
@validates('failure_modes')
def validate_failure_modes(self, key, failure_mode):
# ... your validation logic ...
这些方法中的每一种都有其自身的优缺点,你应该选择最适合你的特定用例的方法。
标签:python,python-3.x,sqlalchemy From: 78796179