SQLAlchemy полиморфный на множественные тождества для класса - PullRequest
0 голосов
/ 05 ноября 2018

Я пытаюсь реализовать наследование одной таблицы с помощью SQLAlchemy. Согласно официальной документации SQLAlchemy (здесь: официальная документация ), способ присвоения одиночной полиморфной идентичности заключается в следующем:

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type
    }

Я хочу назначить несколько идентификаторов для одного класса. Например:

 class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type
    }

class Manager(Employee):
    manager_name = Column(String(30))

    company_id = Column(ForeignKey('company.id'))
    company = relationship("Company", back_populates="managers")

    __mapper_args__ = {
        'polymorphic_identity': ['manager', 'ceo'], 
    }

Обратите внимание на следующую строку:

'polymorphic_identity': ['manager', 'ceo']

Эта строка невозможна (к сожалению).

Я хотел бы знать, есть ли способ присвоения нескольких полиморфных идентификаторов одному классу. К сожалению, я не смог найти такого примера в документации.

1 Ответ

0 голосов
/ 06 ноября 2018

Этот код не будет работать, потому что polymorphic_identity класса используется в качестве словарного ключа. По той же причине, по которой [] in {} поднимает TypeError, то же самое происходит и с вашим кодом.

Обычный способ сделать что-то подобное - создать класс CEO, который был бы подклассом Manager, который мог бы иметь как собственный polymorphic_identity, так и Mapper. Но, кроме всего прочего, вы можете создать второй polymorphic_identity для данного класса (но это довольно хакерски).

Каждый класс, который наследуется от Base, имеет ссылку на тот же polymorphic_map, который является просто dict:

from sqlalchemy.orm import class_mapper
emp_mapper = class_mapper(Employee)
mgr_mapper = class_mapper(Manager)
print(type(emp_mapper.polymorphic_map))  # <class 'dict'>
print(emp_mapper.polymorphic_map is mgr_mapper.polymorphic_map)  # True

polymorphic_map отображает polymorphic_identity в Mapper, так что когда данная строка извлекается из базы данных, значение столбца, назначенного столбцу polymorphic_identity, можно использовать для получения класса, который должен использоваться для представления этих данных. Например, после определения только вашего Employee класса polymorphic_map выглядит так: {'employee': <Mapper at 0x1b1eafcac50; Employee>}. И после определения класса Manager с 'manager' как polymorphic_identity это выглядит так: {'employee': <Mapper at 0x25d10b19cf8; Employee>, 'manager': <Mapper at 0x25d0fdbd4e0; Manager>}

Я создам несколько тестовых данных (мне пришлось удалить все ссылки на таблицу компании - см. MCVE ):

Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
s = Session()
engineer = Employee(name='engineer')
manager1 = Manager(name='manager1')
manager2 = Manager(name='manager2')
s.add_all([engineer, manager1, manager2])
s.commit()
print(s.query(Employee).all())  # [<__main__.Employee object at 0x000001E5E54A0550>, <__main__.Manager object at 0x000001E5E54A0CF8>, <__main__.Manager object at 0x000001E5E54A0D68>]
print(s.query(Manager).all())  # [<__main__.Manager object at 0x000001E5E54A0CF8>, <__main__.Manager object at 0x000001E5E54A0D68>]

Теперь давайте повысим менеджера до генерального директора:

manager1.type = 'ceo'
s.commit()

Это выдает предупреждение:

SAWarning: Flushing object <Manager at 0x1e5e54a0cf8> with incompatible polymorphic identity 'ceo'; the object may not refresh and/or load correctly (this warning may be suppressed after 10 occurrences)

Но мы проигнорируем это и попытаемся запросить таблицу сотрудников:

print(s.query(Employee).all())

Поднимает:

Traceback (most recent call last):
  File "C:\Users\peter_000\.virtualenvs\test-_0Fb_hDQ\lib\site-packages\sqlalchemy\orm\loading.py", line 721, in configure_subclass_mapper
    sub_mapper = mapper.polymorphic_map[discriminator]
KeyError: 'ceo'

Таким образом, столбец type для manager1 теперь имеет значение, которого нет в polymorphic_map, и мы получаем ключевую ошибку. Поскольку нас интересует только когда генеральный директор представлен как Manager, мы можем просто вручную вставить запись в polymorphic_map, связав ключ 'ceo' с Mapper для класса Manager. Например:

mgr_mapper.polymorphic_map['ceo'] = mgr_mapper

Теперь давайте снова запросим таблицу сотрудников:

print(s.query(Employee).all())  #  [<__main__.Employee object at 0x0000020EE7320550>, <__main__.Manager object at 0x0000020EE7320CF8>, <__main__.Manager object at 0x0000020EE7320D68>]

Обратите внимание, что теперь он снова печатает два объекта менеджера.

Отказ от ответственности: Внутри Mapper для Manager он поддерживает ссылку на polymorphic_identity класса (то есть 'manager'), и поэтому наша клавиша ceo в polymorphic_map указывает на маппер который ссылается на polymorphic_identity из manager. Я упоминаю об этом, поскольку в этом примере все работает нормально, я не знаю, может ли это вызвать ошибки в других местах sqlalchemy. Итак, если вы используете что-то подобное в производстве, убедитесь, что оно хорошо протестировано.

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