如何过滤SQLAlchemy association_proxy返回列表的None值且保留原类型
我按如下方式使用association_proxy:
study_participantions = association_proxy("quests", "study_participant",creator=lambda sp: sp.questionnaire)
数据库中包含以下三张表:
PatientStudyParticipantQuestionnaire
表间关联规则:
Patient和Questionnaire为多对一关联关系- 单个
Questionnaire可通过一对一关系归属到某个StudyParticipant StudyParticipant和Patient无直接关联,因为StudyParticipant支持匿名属性- 现有逻辑可通过getter、setter方法经由Questionnaire查询Patient,基于现有代码库开发要求,必须保留Questionnaire内的patient关联字段
当前问题:从Patient侧可通过代理获取关联的StudyParticipant,取值、赋值操作均可正常运行,但当Questionnaire未归属任何StudyParticipant时,返回的数组中会包含None值。需要过滤掉这些None值得到无空值的数组,同时要求返回结果仍为sqlalchemy.ext.associationproxy._AssociationList类型,保证append、remove等列表操作可正常使用。
简化后的模型类定义如下:
class Patient(Model): __tablename__ = 'patient' id = Column(Integer, primary_key=True) study_participantions = association_proxy("quests", "study_participant",creator=lambda sp: sp.questionnaire) class StudyParticipant(Model): #better name would be participation __tablename__ = "study_participant" id = Column(Integer, primary_key=True) pseudonym = Column(String(40), nullable = True) questionnaire = relationship("Questionnaire", backref="study_participant",uselist=False) # why go via the StudyQuestionnaire class Questionnaire(Model, metaclass=QuestionnaireMeta): __tablename__ = 'questionnaire' id = Column(Integer, primary_key=True) patient_id = Column(Integer(), ForeignKey('patient.id'), nullable=True) patient = relationship('Patient', backref='quests', primaryjoin=questionnaire_patient_join)
两种无侵入实现方式,都可以保留AssociationList的所有原生操作能力,不会破坏append、remove等方法的逻辑:
方案1:自定义AssociationList子类自动过滤空值
直接继承SQLAlchemy原生的_AssociationList,重写列表初始化、元素新增的逻辑,自动过滤掉None值,再把这个自定义类传给association_proxy的list_type参数即可,不需要改动其他关联逻辑。
from sqlalchemy.ext.associationproxy import _AssociationList class FilteredAssociationList(_AssociationList): def __init__(self, lazy_collection, creator, getter, setter, parent): super().__init__(lazy_collection, creator, getter, setter, parent) # 初始化时过滤已存在的None值 self[:] = [item for item in self if item is not None] def append(self, item): # 新增元素时拦截None值 if item is not None: super().append(item) def insert(self, index, item): if item is not None: super().insert(index, item) def __setitem__(self, index, item): if item is not None: super().__setitem__(index, item) # 修改Patient类的association_proxy定义,指定自定义列表类型 class Patient(Model): __tablename__ = 'patient' id = Column(Integer, primary_key=True) study_participantions = association_proxy( "quests", "study_participant", creator=lambda sp: sp.questionnaire, list_type=FilteredAssociationList )
这种方式完全保留原生AssociationList的所有行为,所有增删操作都会自动跳过None值,查询返回结果中不会出现空值。
方案2:在Questionnaire关联层面做前置过滤
如果不想自定义列表类,也可以直接给quests关系增加过滤条件,只拉取绑定了StudyParticipant的Questionnaire记录,从根源上避免出现None值:
from sqlalchemy.orm import backref from sqlalchemy import and_ class Questionnaire(Model, metaclass=QuestionnaireMeta): __tablename__ = 'questionnaire' id = Column(Integer, primary_key=True) patient_id = Column(Integer(), ForeignKey('patient.id'), nullable=True) # 注意:如果关联StudyParticipant的外键字段名不是study_participant_id,替换为实际字段名即可 patient = relationship( 'Patient', backref=backref( 'quests', primaryjoin=and_( questionnaire_patient_join, Questionnaire.study_participant_id != None ) ), primaryjoin=questionnaire_patient_join )
注意:这种方式会改变quests关联的返回结果,如果其他业务逻辑需要通过patient.quests拿到所有问卷(包括未绑定StudyParticipant的记录),不要使用该方案,优先选择方案1。
两种方案都不需要改动现有creator逻辑,append、remove等操作和原生行为完全一致,返回结果仍然是AssociationList子类,兼容所有列表类操作。
内容的提问来源于stack exchange,提问作者Alex

