#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)
Все еще есть некоторые предупреждения, но теперь код работает так, как задумано.