Один из способов добиться этого - использовать Mixin
, который использует declared_attr.cascading
.
Вот класс mixin:
class Mixin:
@declared_attr.cascading
def related_entity(cls):
if has_inherited_table(cls):
return relationship(
'RelatedEntity',
backref=cls.__name__.lower(),
uselist=False
)
Флаг cascading
на declared_attr
сделает попытку sqlalchemy визуализировать атрибут 'mixed in' для каждого класса в иерархии. Или, как сказано в документации:
Это модификатор специального назначения, который указывает, что столбец или
Объявленный атрибут на основе MapperProperty должен быть настроен
отчетливо для каждого отображаемого подкласса в сценарии отображенного наследования.
Функция has_inherited_table()
позволяет нам определять в миксине, имеем ли мы дело с BaseEntity
или подклассом, так что мы только добавляем отношения к подклассам.
Затем миксин наследуется в модель BaseEntity
:
class BaseEntity(Base, Mixin):
id = sa.Column(sa.Integer, primary_key=True)
related_id = sa.Column(
sa.Integer, sa.ForeignKey('relatedentity.id'))
discriminator = sa.Column(sa.String)
@declared_attr
def __mapper_args__(cls):
if has_inherited_table(cls):
args = {'polymorphic_identity': cls.__name__.lower()}
else:
args = {'polymorphic_on': cls.discriminator}
return args
Как вы упомянули в своем вопросе, что вы используете наследование объединенной таблицы, я определил __mapper_args__
для BaseEntity
с помощью метода @declared_attr
, чтобы polymorphic_identity
также можно было автоматически генерировать из имени класса для подклассов.
Таким образом, в этой конфигурации каждый подкласс BaseEntity
будет применять атрибут отношения к RelatedEntity
, названному в честь подкласса. Вот полный рабочий пример:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import (declarative_base, declared_attr,
has_inherited_table)
from sqlalchemy.orm import relationship, sessionmaker
class BaseClass:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
Base = declarative_base(cls=BaseClass)
engine = sa.create_engine('sqlite://', echo=False)
Session = sessionmaker(bind=engine)
class Mixin:
@declared_attr.cascading
def related_entity(cls):
if has_inherited_table(cls):
return relationship(
'RelatedEntity',
backref=cls.__name__.lower(),
uselist=False
)
class BaseEntity(Base, Mixin):
id = sa.Column(sa.Integer, primary_key=True)
related_id = sa.Column(
sa.Integer, sa.ForeignKey('relatedentity.id'))
discriminator = sa.Column(sa.String)
@declared_attr
def __mapper_args__(cls):
if has_inherited_table(cls):
args = {'polymorphic_identity': cls.__name__.lower()}
else:
args = {'polymorphic_on': cls.discriminator}
return args
class RelatedEntity(Base):
""" Class that is related to all `BaseEntity` subclasses"""
id = sa.Column(sa.Integer, primary_key=True)
class SubEntity(BaseEntity):
""" Will generate `RelatedEntity.subentity`"""
id = sa.Column(sa.Integer, sa.ForeignKey('baseentity.id'),
primary_key=True)
class OtherEntity(BaseEntity):
""" Will generate `RelatedEntity.otherentity`"""
id = sa.Column(sa.Integer, sa.ForeignKey('baseentity.id'),
primary_key=True)
if __name__ == '__main__':
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
s = Session()
rel_inst = RelatedEntity()
s.add(rel_inst)
rel_inst.subentity.append(SubEntity())
rel_inst.otherentity.append(OtherEntity())
s.commit()
print(rel_inst.subentity, rel_inst.otherentity)
# [<__main__.SubEntity object at 0x0000023487D42C18>] [<__main__.OtherEntity object at 0x0000023487D60278>]
Причина, по которой мы не можем определить метод related_entity()
declared_attr
в BaseModel
, заключается в том, что SQLAlchemy не будет учитывать каскад и не будет сгенерировано никаких связей (поскольку блок if has_inherited_table(cls):
предотвращает BaseModel
от порождающий один). С документы :
Этот флажок применяется только к использованию Decla_attr в декларативном mixin
классы и __abstract__
классы; в настоящее время это не имеет никакого эффекта при использовании
непосредственно на сопоставленный класс.