В SQLAlchemy можно ли переменной `backref` внутри` отношения` назначать переменную? - PullRequest
0 голосов
/ 23 апреля 2019

Здесь я использую наследование таблиц соединений.

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, но оно должно быть в состоянии наследоваться неограниченным подклассом), мне нужно depends_on_who_inherits_me часть переменной или, более конкретно, имя наследующего подкласса, а не строка. Таким образом, у каждого подкласса будет отношение, относящееся к стороннему классу, при этом оно будет ссылаться на этот конкретный подкласс по соответствующему имени.

Однако, поскольку это за пределами любого метода, я не могу использовать self для гибкой ссылки на экземпляры.

Как реализовать эту идею? Спасибо.

1 Ответ

1 голос
/ 23 апреля 2019

Один из способов добиться этого - использовать 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__ классы; в настоящее время это не имеет никакого эффекта при использовании непосредственно на сопоставленный класс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...