DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
晚安云;香港 CN2 ¥15/月
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关

SQLAlchemy 两个无关泛型模型报 instrumentation 冲突

两个仅共享 Generic 的 ORM 类使用不同 instrumentation,却在注册时被误判成同一继承网络。说明 SQLAlchemy 2.1.6 的 Generic 遍历边界和 2.0 维护线限制。

两个 ORM 类并没有业务继承关系,仅各自继承了 Generic[T],映射第二个类时却出现 TypeError: multiple instrumentation implementations specified。如果其中一个类使用自定义 InstrumentationManager,先检查 SQLAlchemy 对类层次的遍历,而不是合并两套模型或改数据库表。

已知触发包括 SQLAlchemy 2.0.54、2.1.4 与 Python 3.13.13:一个泛型类选择自定义 instrumentation,另一个泛型类使用默认实现。它们唯一共同祖先是 typing.Generic 和 object,但冲突检查沿 Generic 找到了其他子类,把两者误放进同一继承网络。

查看类定义时就能区分的条件

问题发生在 registry.map_imperatively() 注册类的阶段,无需数据库连接便能触发。下面是需要重点比对的结构,T 只是类型参数:

from typing import Generic, TypeVar
from sqlalchemy.ext.instrumentation import InstrumentationManager

T = TypeVar("T")

class Custom(Generic[T]):
    __sa_instrumentation_manager__ = InstrumentationManager

class Native(Generic[T]):
    pass

这里展示类结构,不是完整映射程序。应继续对照项目原来的 registry、表和管理器实现,确认没有真正共享带 instrumentation 的业务基类。真实继承链存在不同实现时,冲突检查可能合理,不能看到同一报错就一律绕过。

这与 Python 3.14.1 的 dataclass mixin 注解缺失不同:后者会在构造签名和延迟注解中报 KeyError;这里检查的是类注册时的 instrumentation 实现归属。

2.1.6 的正式实现已停止沿 Generic 扩散

SQLAlchemy 2.1.6 的 class_hierarchy() 已将 typing.Generic 作为停止展开子类的边界。它仍会处理类自己的真实继承关系,不再因为共享 Generic 而遍历整个进程中无关的泛型类。

项目已使用 2.1 且允许更新时,可采用包含该处理的兼容正式版本,在原来的导入顺序下复核。2.0.55 的正式源码尚未包含同一边界处理;继续维护 2.0 的项目,不应把尚未正式发行的后续修正当成可安装版本,也不宜仅为这一点直接跨系列升级。

临时方案需要由模型设计决定。两类本来就允许共用一种管理器时,可以评估统一实现;确实需要不同管理器时,应保留这种需求,选择兼容环境或等待正式维护版本。删除 Generic 会改变类型接口,取消自定义管理器会改变对象行为,都不是无影响的通用修复。直接修改私有冲突检查器也容易屏蔽真正的继承冲突。

复核应包含两类按不同顺序注册、两个管理器各自的对象行为,以及应用实际的持久化和属性变更路径。只让第二个类不再报错,并不能证明它用了正确的管理器。保留原依赖、模型和 registry 配置;此处类遍历修复本身不需要数据库迁移。