【问题标题】:How to structure dataclass with cattrs on sqlalchemy classical mapping?如何在 sqlalchemy 经典映射上使用 cattrs 构造数据类?
【发布时间】:2022-01-10 02:51:18
【问题描述】:

我正在尝试在我使用 sqlalchemy 的应用程序中使用 cattr 构造一些类。出于某种原因,在我向数据库提交一个类后,无法构造回任何嵌套的数据类。我得到了错误:

in emit_backref_from_collection_append_event
    child_state, child_dict = instance_state(child), instance_dict(child)
AttributeError: 'dict' object has no attribute '_sa_instance_state'

这是一个代码示例。

from dataclasses import dataclass
from sqlalchemy.orm import backref, relationship, registry, sessionmaker
from sqlalchemy import Table, Column, ForeignKey, create_engine
from sqlalchemy.sql.sqltypes import Integer, String
import cattr

from src.vlep.domain.model import Patient

mapper_registry = registry()

parents = Table(
    'parents', mapper_registry.metadata,
    Column('id', Integer, primary_key=True, autoincrement=True),
    Column('name', String(255)),
)

children = Table(
    'children', mapper_registry.metadata,
    Column('id', Integer, primary_key=True, autoincrement=True),
    Column('name', String(255)),
    Column('parent_id', ForeignKey('parents.id', ondelete='SET NULL'))
)


def start_mappers():
    children_mapper = mapper_registry.map_imperatively(
        Child,
        children,
    )
    parent_mapper = mapper_registry.map_imperatively(
        Parent,
        parents,
        properties={
            'children': relationship(
                children_mapper,
                backref=backref('parents'))
        }
    )


@dataclass
class Child:

    name: str


@dataclass
class Parent:

    name: str
    children: list


if __name__ == "__main__":

    engine = create_engine("sqlite:///:memory:")
    mapper_registry.metadata.create_all(engine)
    start_mappers()
    session = sessionmaker(bind=engine)()

    c1 = Child('Uguinho')
    c2 = Child('Luizinho')
    p = Parent('Fulano', [c1, c2])

    session.add(p)
    session.commit()

    try:
        # This works
        d = cattr.unstructure(p, Child)
        p2 = cattr.structure(d, Child)
        d2 = cattr.unstructure(p2, Child)
        print(d2)
    finally:
        # This does not
        d = cattr.unstructure(p, Parent)
        p2 = cattr.structure(d, Parent)
        d2 = cattr.unstructure(p2, Parent)
        print(d2)

通过错误消息,我猜 sqlalchemy 正在尝试检查 parent_mapper 上的属性“children”是否是 Child 的列表,而它正在获取 dict(它处于非结构化状态) )。我实际上什至不知道 sqlalchemy 是如何进行这项检查的。再次猜测,我想它总是会检查一致性。无论如何,我现在知道如何解决这个问题了。

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    所以我对 SQLAlchemy 文档进行了一些挖掘,发现并改编了an example,它可以让您的代码正常工作:

    from dataclasses import dataclass
    from typing import List
    
    import cattr
    from sqlalchemy.orm import (
        relationship,
        sessionmaker,
        registry,
    )
    from sqlalchemy import Table, Column, ForeignKey, create_engine, MetaData
    from sqlalchemy.sql.sqltypes import Integer, String
    
    # commented this out for the example
    # from src.vlep.domain.model import Patient
    
    mapper_registry = registry()
    
    
    @dataclass
    class Child:
    
        name: str
    
    
    @dataclass
    class Parent:
    
        name: str
        children: List[Child]
    
    
    metadata_obj = MetaData()
    
    parent = Table(
        "parent",
        metadata_obj,
        Column("id", Integer, primary_key=True, autoincrement=True),
        Column("name", String(255)),
    )
    
    child = Table(
        "child",
        metadata_obj,
        Column("id", Integer, primary_key=True, autoincrement=True),
        Column("name", String(255)),
        Column("parent_id", ForeignKey("parent.id", ondelete="SET NULL")),
    )
    
    
    def start_mappers():
        mapper_registry.map_imperatively(
            Parent,
            parent,
            properties={"children": relationship(Child, backref="parent")},
        )
        mapper_registry.map_imperatively(Child, child)
    
    
    if __name__ == "__main__":
    
        engine = create_engine("sqlite:///:memory:")
        metadata_obj.create_all(engine)
        start_mappers()
        session = sessionmaker(bind=engine)()
    
        c1 = Child("Uguinho")
        c2 = Child("Luizinho")
        p = Parent("Fulano", [c1, c2])
    
        session.add(p)
        session.commit()
    
        try:
            # This works
            d = cattr.unstructure(p, Child)
            p2 = cattr.structure(d, Child)
            d2 = cattr.unstructure(p2, Child)
            print(d2)
        finally:
            # This now works
            d = cattr.unstructure(p, Parent)
            p2 = cattr.structure(d, Parent)
            d2 = cattr.unstructure(p2, Parent)
            print(d2)
    

    输出:

    {'name': 'Fulano'}
    {'name': 'Fulano', 'children': [{'name': 'Uguinho'}, {'name': 'Luizinho'}]}
    

    我认为主要问题之一可能是您将“backref”声明为backref=backref('parents'),而那里的函数包装器是不必要的?不太确定...

    无论如何,数据类现在已正确用于映射(据我所知)。我希望这个例子对你有用!

    【讨论】:

    • Tks 伙计。有用。这里的问题实际上是没有正确设置类型List[Child]。包装器放在那里是因为在我的实际应用程序中我也设置了lazy
    • 啊,很高兴知道,很高兴你把它整理出来!
    猜你喜欢
    • 2013-09-19
    • 2021-11-06
    • 1970-01-01
    • 2014-04-08
    • 2021-08-23
    • 1970-01-01
    • 2017-08-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多