You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLAlchemy命令式映射复合实体:无需__composite_values__及第三方依赖

可行的映射方案

方案1:自定义TypeDecorator封装复合类型转换

用SQLAlchemy的TypeDecorator写一个自定义类型,专门处理Money对象和数据库两列的双向转换,完全不用改动原有的Money和Transaction类。

代码示例

from sqlalchemy import TypeDecorator, Integer, String, Column, BigInteger, DateTime, text
from sqlalchemy.sql import expression
from sqlalchemy.orm import registry
from dataclasses import dataclass
import datetime

mapper_registry = registry()

@dataclass(kw_only=True)
class Money:
    amount: int
    currency: str

@dataclass(kw_only=True)
class Transaction:
    id: int
    value: Money
    description: str
    timestamp: datetime.datetime

class MoneyType(TypeDecorator):
    impl = expression.ColumnClauseList(Integer, String)
    cache_ok = True

    def process_bind_param(self, value, dialect):
        if not value:
            return (None, None)
        return (value.amount, value.currency)

    def process_result_value(self, value, dialect):
        if not value:
            return None
        amount, currency = value
        return Money(amount=amount, currency=currency)

transaction_table = Table(
    "transaction",
    mapper_registry.metadata,
    Column("id", BigInteger, primary_key=True),
    Column("description", String(1024)),
    Column(
        "timestamp",
        DateTime(timezone=False),
        nullable=False,
        server_default=text("NOW()"),
    ),
    # 用自定义类型关联两个实际列
    Column("value", MoneyType, (
        Column("value_amount", Integer(), nullable=False),
        Column("value_currency", String(5), nullable=False),
    ))
)

mapper_registry.map_imperatively(
    Transaction,
    transaction_table,
    properties={"value": transaction_table.c.value}
)

方案2:手动实现属性的getter/setter

在命令式映射里,直接给value属性定义读写逻辑,绕过composite函数对__composite_values__的强制要求。读写value时自动和value_amount、value_currency列做转换。

代码示例

mapper_registry.map_imperatively(
    Transaction,
    transaction_table,
    properties={
        "value": property(
            # 读取时从列数据构造Money对象
            lambda obj: Money(amount=obj.value_amount, currency=obj.value_currency),
            # 赋值时把Money的属性拆分到对应列
            lambda obj, val: (setattr(obj, "value_amount", val.amount), setattr(obj, "value_currency", val.currency))
        )
    },
    # 要把底层的两个列也加入映射,不然实例访问不到
    include_properties=["id", "description", "timestamp", "value_amount", "value_currency"]
)

方案3:用CompositeType+自定义expander参数

用SQLAlchemy的CompositeType定义数据库层面的复合类型,然后在composite映射时,通过expander参数指定如何从Money对象提取字段,不用给原类加__composite_values__方法。

代码示例

from sqlalchemy import CompositeType

# 定义数据库复合类型
money_type = CompositeType(
    "money_type",
    [
        Column("amount", Integer),
        Column("currency", String(5))
    ]
)

transaction_table = Table(
    "transaction",
    mapper_registry.metadata,
    Column("id", BigInteger, primary_key=True),
    Column("description", String(1024)),
    Column(
        "timestamp",
        DateTime(timezone=False),
        nullable=False,
        server_default=text("NOW()"),
    ),
    Column("value", money_type, nullable=False)
)

mapper_registry.map_imperatively(
    Transaction,
    transaction_table,
    properties={
        "value": composite(
            Money,
            transaction_table.c.value.amount,
            transaction_table.c.value.currency,
            # 自定义提取字段的逻辑,替代__composite_values__
            expander=lambda money_obj: (money_obj.amount, money_obj.currency)
        )
    }
)

内容的提问来源于stack exchange,提问作者Andrew Lalis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 11:15:35