#python #sqlalchemy
#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)
Поскольку конкретное имя обратной ссылки станет известно только во время выполнения (в данном случае оно должно быть SubEntity
, но оно должно быть наследуемым неограниченным количеством подклассов), мне нужно depends_on_who_inherits_me
указать переменную, или, более конкретно, имя наследующего подкласса, вместо строки. Таким образом, каждый подкласс будет иметь отношение, ссылающееся на сторонний класс, при этом он будет ссылаться на этот конкретный подкласс по его соответствующему имени.
Однако, поскольку это вне какого-либо метода, я не могу использовать self
для гибкой ссылки на экземпляры.
Как реализовать эту идею? Спасибо.
Ответ №1:
Один из способов, которым вы могли бы достичь этого, — это 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__
on BaseEntity
с помощью @declared_attr
метода, чтобы polymorphic_identity
также можно было автоматически генерировать из имени класса для подклассов.
Таким образом, при такой конфигурации каждый подкласс BaseEntity
будет применять атрибут relationship к 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
их создание). Из документов:
Флаг применяется только к использованию declared_attr в декларативных смешанных классах и
__abstract__
классах; в настоящее время он не имеет эффекта при непосредственном использовании в сопоставленном классе.
Комментарии:
1. Спасибо за этот подробный ответ. После прочтения документации на
declared_attr
я все еще озадачен, почему вы используете.cascading
внутриMixin
, но не вBaseEntity
. Вы упомянули, что это для создания отдельного экземпляра для каждого подкласса. Должны ли эти два случая рассматриваться как равные в этом смысле?2. Я предоставил обновление для своего ответа, но, короче говоря, SQLAlchemy не обрабатывает два случая одинаково (т. Е.
related_entity()
Определенные наMixin
иBaseModel
не ведут себя одинаково.3. Пожалуйста, поправьте меня, если я неправильно понимаю: наличие
cascading
или без него влияет только на подклассы третьего поколения и далее, это не влияет на второе поколение? Из вашего примераSubEntity
все еще может наследоваться__tablename__
безcascading
настройки.4. Кроме того, почему наличие отображенной таблицы является надежным индикатором того, является ли она BaseEntity или SubEntity? Не могли бы вы дать небольшой намек здесь? Спасибо!
5.Что касается разницы между
__tablename__
иrelationship
declared_attr
, вы читали это? docs.sqlalchemy.org/en/13/orm/extensions/declarative /….