Поведение класса данных SQLAlchemy и декларативные сочетания: ошибка атрибута: не удается установить атрибут

#python #sqlalchemy #python-dataclasses

#питон #sqlalchemy #python-классы данных

Вопрос:

У меня есть набор классов, которые я разработал как чистые классы данных python. Вот урезанный пример, который ведет себя так, как я ожидал:

 from dataclasses import dataclass from abc import ABC  @dataclass class Owner:  name: str = ""  @dataclass class OwnedThing(ABC):  owner: Owner = None  @dataclass class Thing1(OwnedThing):  name: str = ""   owner = Owner(name="foo") thing = Thing1( name="bar", owner=owner) print(thing.owner.name)  

Здесь у нас есть базовый класс, который устанавливает, что все производные классы будут иметь объект «владелец». Назначение владельца несложно.

Когда я попытался добавить SQLAlchemy для сохранения, я хотел бы сохранить общую структуру наследования, чтобы мне не приходилось повторять эту связь между владельцем и вещью для всех разновидностей принадлежащих вещей. Однако, когда я это сделаю, я больше не смогу назначить экземпляр класса владельца .owner атрибуту:

 from dataclasses import dataclass,field from abc import ABC from sqlalchemy.orm import declarative_mixin, declared_attr, relationship from sqlalchemy import Column, ForeignKey  @dataclass class Owner:  name: str = ""  @declarative_mixin @dataclass class OwnedThing(ABC):  owner_id: int = field(init=False, default=None)  owner: Owner = None   @declared_attr  def owner_id(cls):  return Column('owner_id', ForeignKey('owner.id'))   @declared_attr  def owner(cls):  return relationship(Owner)  @dataclass class Thing1(OwnedThing):  name: str = ""   owner = Owner(name="foo") thing = Thing1( name="bar", owner=owner) print(thing.owner.name)  

Это приводит к такой трассировке стека:

 /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/dataclasses.py:658: SAWarning: Unmanaged access of declarative attribute owner_id from non-mapped class OwnedThing  default = getattr(cls, a_name, MISSING) /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/dataclasses.py:658: SAWarning: Unmanaged access of declarative attribute owner from non-mapped class OwnedThing  default = getattr(cls, a_name, MISSING) /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/dataclasses.py:842: SAWarning: Unmanaged access of declarative attribute owner_id from non-mapped class OwnedThing  if isinstance(getattr(cls, f.name, None), Field): /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/dataclasses.py:842: SAWarning: Unmanaged access of declarative attribute owner from non-mapped class OwnedThing  if isinstance(getattr(cls, f.name, None), Field): Traceback (most recent call last):  File "test_case_sql.py", line 30, in lt;modulegt;  thing = Thing1( name="bar", owner=owner)  File "lt;stringgt;", line 2, in __init__ AttributeError: can't set attribute  

Note that when I skip having a base class, things work as I’d expect, without error:

 from dataclasses import dataclass,field from abc import ABC from sqlalchemy.orm import declarative_mixin, declared_attr, relationship from sqlalchemy import Column, ForeignKey,Integer  @dataclass class Owner:  name: str = ""   @dataclass class Thing1:  owner_id: int = Column(Integer)  owner: Owner = relationship("Owner")  name: str = ""   owner = Owner(name="foo") thing = Thing1( name="bar", owner=owner) print(thing.owner.name)  

Я недостаточно разбираюсь во внутренних компонентах SQLAlchemy, чтобы понять, что я делаю неправильно, и я не видел, чтобы кто-то еще сталкивался с этой проблемой раньше. Может ли кто-нибудь помочь мне продолжать использовать классы данных и миксины?

Ответ №1:

Оказывается, добавление кода сопоставления вокруг классов устраняет проблему:

 from dataclasses import dataclass,field from abc import ABC from sqlalchemy.orm import declarative_mixin, declared_attr, relationship, registry from sqlalchemy import Column, ForeignKey, Integer  mapper_registry = registry()  @mapper_registry.mapped @dataclass class Owner:  __tablename__ = "owner"  id: int = Column(Integer, primary_key=True)   name: str = ""  @declarative_mixin @dataclass class OwnedThing(ABC):  id: int = Column(Integer, primary_key=True)  owner_id: int = field(init=False, default=None)  owner: Owner = None   @declared_attr  def owner_id(cls):  return Column('owner_id', ForeignKey('owner.id'))   @declared_attr  def owner(cls):  return relationship(Owner)  @mapper_registry.mapped @dataclass class Thing1(OwnedThing):  __tablename__ = "thing"   name: str = ""   owner = Owner(name="foo") thing = Thing1( name="bar", owner=owner) print(thing.owner.name)  

Все еще есть некоторые предупреждения, но теперь код работает так, как задумано.