在SQLAlchemy中,可以为“ relationship”内部的“ backref”参数分配一个变量吗?

时间:2019-04-23 09:59:02

标签: python sqlalchemy

我在这里使用联接表继承。

class BaseEntity(Base):
    some_col = Column(String)
    base_relationship = relationship("some_relationship", backref="depends_on_who_inherits_me")

class SubEntity(BaseEntity):
    some_unique_col = Column(String)

由于特定的backref名称仅在运行时才知道(在这种情况下,它应该为SubEntity,但是它应该可以被无限的子类继承),因此我需要{{1} }组成一个变量,或更具体地说,继承子类的名称,而不是字符串。因此,每个子类都有一个引用第三方类的关系,同时又用其适当的名称引用该特定子类。

但是,由于这是任何方法之外的,所以我不能使用depends_on_who_inherits_me灵活地引用实例。

如何实现这个想法?谢谢。

1 个答案:

答案 0 :(得分:1)

一种实现此目标的方法是使用Mixindeclared_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()函数使我们能够在mixin中确定是要处理BaseEntity还是子类,因此我们仅将关系添加到子类上。

然后将mixin继承到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不会遵循级联,并且不会生成任何关系(因为{{1 }}阻止if has_inherited_table(cls):生成一个)。来自the docs

  

该标志仅适用于在声明性mixin上使用clarified_attr   类和BaseModel类;目前使用时无效   直接在映射的类上。