import sys import typing from typing import Any, Mapping, get_args, get_origin, get_type_hints from pydantic import ConfigDict from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined as Undefined from sqlalchemy.orm import Mapped from sqlmodel import Field, SQLModel from sqlmodel.main import SQLModelMetaclass # Python 3.14+ PEP 649支持 if sys.version_info >= (3, 14): import annotationlib # 全局Monkey-patch: 修复SQLModel在Python 3.14上的兼容性问题 import sqlmodel.main _original_get_sqlalchemy_type = sqlmodel.main.get_sqlalchemy_type def _patched_get_sqlalchemy_type(field): """ 修复SQLModel的get_sqlalchemy_type函数,处理Python 3.14的类型问题。 问题: 1. ForwardRef对象(来自Relationship字段)会导致issubclass错误 2. typing._GenericAlias对象(如ClassVar[T])也会导致同样问题 3. list/dict等泛型类型在没有Field/Relationship时可能导致错误 4. Mapped类型在Python 3.14下可能出现在annotation中 5. Annotated类型可能包含sa_type metadata(如Array[T]) 6. 自定义类型(如NumpyVector)有__sqlmodel_sa_type__属性 7. Pydantic已处理的Annotated类型会将metadata存储在field.metadata中 解决: - 优先检查field.metadata中的__get_pydantic_core_schema__(Pydantic已处理的情况) - 检测__sqlmodel_sa_type__属性(NumpyVector等) - 检测Relationship/ClassVar等返回None - 对于Annotated类型,尝试提取sa_type metadata - 其他情况调用原始函数 """ # 优先检查 field.metadata(Pydantic已处理Annotated类型的情况) # 当使用 Array[T] 或 Annotated[T, metadata] 时,Pydantic会将metadata存储在这里 metadata = getattr(field, 'metadata', None) if metadata: # metadata是一个列表,包含所有Annotated的元数据项 for metadata_item in metadata: # 检查metadata_item是否有__get_pydantic_core_schema__方法 if hasattr(metadata_item, '__get_pydantic_core_schema__'): try: # 调用获取schema schema = metadata_item.__get_pydantic_core_schema__(None, None) # 检查schema的metadata中是否有sa_type if isinstance(schema, dict) and 'metadata' in schema: sa_type = schema['metadata'].get('sa_type') if sa_type is not None: return sa_type except (TypeError, AttributeError, KeyError): # Pydantic schema获取可能失败(类型不匹配、缺少属性等) # 这是正常情况,继续检查下一个metadata项 pass annotation = getattr(field, 'annotation', None) if annotation is not None: # 优先检查 __sqlmodel_sa_type__ 属性 # 这处理 NumpyVector[dims, dtype] 等自定义类型 if hasattr(annotation, '__sqlmodel_sa_type__'): return annotation.__sqlmodel_sa_type__ # 检查自定义类型(如JSON100K)的 __get_pydantic_core_schema__ 方法 # 这些类型在schema的metadata中定义sa_type if hasattr(annotation, '__get_pydantic_core_schema__'): try: # 调用获取schema(传None作为handler,因为我们只需要metadata) schema = annotation.__get_pydantic_core_schema__(annotation, lambda x: None) # 检查schema的metadata中是否有sa_type if isinstance(schema, dict) and 'metadata' in schema: sa_type = schema['metadata'].get('sa_type') if sa_type is not None: return sa_type except (TypeError, AttributeError, KeyError): # Schema获取失败,继续其他检查 pass anno_type_name = type(annotation).__name__ # ForwardRef: Relationship字段的annotation if anno_type_name == 'ForwardRef': return None # AnnotatedAlias: 检查是否有sa_type metadata(如Array[T]) if anno_type_name == 'AnnotatedAlias' or anno_type_name == '_AnnotatedAlias': from typing import get_origin, get_args import typing # 尝试提取Annotated的metadata if hasattr(typing, 'get_args'): args = get_args(annotation) # args[0]是实际类型,args[1:]是metadata for metadata in args[1:]: # 检查metadata是否有__get_pydantic_core_schema__方法 if hasattr(metadata, '__get_pydantic_core_schema__'): try: # 调用获取schema schema = metadata.__get_pydantic_core_schema__(None, None) # 检查schema中是否有sa_type if isinstance(schema, dict) and 'metadata' in schema: sa_type = schema['metadata'].get('sa_type') if sa_type is not None: return sa_type except (TypeError, AttributeError, KeyError): # Annotated metadata的schema获取可能失败 # 这是正常的类型检查过程,继续检查下一个metadata pass # _GenericAlias或GenericAlias: typing泛型类型 if anno_type_name in ('_GenericAlias', 'GenericAlias'): from typing import get_origin import typing origin = get_origin(annotation) # ClassVar必须跳过 if origin is typing.ClassVar: return None # list/dict/tuple/set等内置泛型,如果字段没有明确的Field或Relationship,也跳过 # 这通常意味着它是Relationship字段或类变量 if origin in (list, dict, tuple, set): # 检查field_info是否存在且有意义 # Relationship字段会有特殊的field_info field_info = getattr(field, 'field_info', None) if field_info is None: return None # Mapped: SQLAlchemy 2.0的Mapped类型,SQLModel不应该处理 # 这可能是从父类继承的字段或Python 3.14注解处理的副作用 # 检查类型名称和annotation的字符串表示 if 'Mapped' in anno_type_name or 'Mapped' in str(annotation): return None # 检查annotation是否是Mapped类或其实例 try: from sqlalchemy.orm import Mapped as SAMapped # 检查origin(对于Mapped[T]这种泛型) from typing import get_origin if get_origin(annotation) is SAMapped: return None # 检查类型本身 if annotation is SAMapped or isinstance(annotation, type) and issubclass(annotation, SAMapped): return None except (ImportError, TypeError): # 如果SQLAlchemy没有Mapped或检查失败,继续 pass # 其他情况正常处理 return _original_get_sqlalchemy_type(field) sqlmodel.main.get_sqlalchemy_type = _patched_get_sqlalchemy_type # 第二个Monkey-patch: 修复继承表类中InstrumentedAttribute作为默认值的问题 # 在Python 3.14 + SQLModel组合下,当子类(如SMSBaoProvider)继承父类(如VerificationCodeProvider)时, # 父类的关系字段(如server_config)会在子类的model_fields中出现, # 但其default值错误地设置为InstrumentedAttribute对象,而不是None # 这导致实例化时尝试设置InstrumentedAttribute为字段值,触发SQLAlchemy内部错误 import sqlmodel._compat as _compat from sqlalchemy.orm import attributes as _sa_attributes _original_sqlmodel_table_construct = _compat.sqlmodel_table_construct def _patched_sqlmodel_table_construct(self_instance, values): """ 修复sqlmodel_table_construct,跳过InstrumentedAttribute默认值 问题: - 继承自polymorphic基类的表类(如FishAudioTTS, SMSBaoProvider) - 其model_fields中的继承字段default值为InstrumentedAttribute - 原函数尝试将InstrumentedAttribute设置为字段值 - SQLAlchemy无法处理,抛出 '_sa_instance_state' 错误 解决: - 只设置用户提供的值和非InstrumentedAttribute默认值 - InstrumentedAttribute默认值跳过(让SQLAlchemy自己处理) """ cls = type(self_instance) # 收集要设置的字段值 fields_to_set = {} for name, field in cls.model_fields.items(): # 如果用户提供了值,直接使用 if name in values: fields_to_set[name] = values[name] continue # 否则检查默认值 # 跳过InstrumentedAttribute默认值 - 这些是继承字段的错误默认值 if isinstance(field.default, _sa_attributes.InstrumentedAttribute): continue # 使用正常的默认值 if field.default is not Undefined: fields_to_set[name] = field.default elif field.default_factory is not None: fields_to_set[name] = field.get_default(call_default_factory=True) # 设置属性 - 只设置非InstrumentedAttribute值 for key, value in fields_to_set.items(): if not isinstance(value, _sa_attributes.InstrumentedAttribute): setattr(self_instance, key, value) # 设置Pydantic内部属性 object.__setattr__(self_instance, '__pydantic_fields_set__', set(values.keys())) if not cls.__pydantic_root_model__: _extra = None if cls.model_config.get('extra') == 'allow': _extra = {} for k, v in values.items(): if k not in cls.model_fields: _extra[k] = v object.__setattr__(self_instance, '__pydantic_extra__', _extra) if cls.__pydantic_post_init__: self_instance.model_post_init(None) elif not cls.__pydantic_root_model__: object.__setattr__(self_instance, '__pydantic_private__', None) # 设置关系 for key in self_instance.__sqlmodel_relationships__: value = values.get(key, Undefined) if value is not Undefined: setattr(self_instance, key, value) return self_instance _compat.sqlmodel_table_construct = _patched_sqlmodel_table_construct else: annotationlib = None def _extract_sa_type_from_annotation(annotation: Any) -> Any | None: """ 从类型注解中提取SQLAlchemy类型。 支持以下形式: 1. NumpyVector[256, np.float32] - 直接使用类型(有__sqlmodel_sa_type__属性) 2. Annotated[np.ndarray, NumpyVector[256, np.float32]] - Annotated包装 3. 任何有__get_pydantic_core_schema__且返回metadata['sa_type']的类型 Args: annotation: 字段的类型注解 Returns: 提取到的SQLAlchemy类型,如果没有则返回None """ # 方法1:直接检查类型本身是否有__sqlmodel_sa_type__属性 # 这涵盖了 NumpyVector[256, np.float32] 这种直接使用的情况 if hasattr(annotation, '__sqlmodel_sa_type__'): return annotation.__sqlmodel_sa_type__ # 方法2:检查是否为Annotated类型 if get_origin(annotation) is typing.Annotated: # 获取元数据项(跳过第一个实际类型参数) args = get_args(annotation) if len(args) >= 2: metadata_items = args[1:] # 第一个是实际类型,后面都是元数据 # 遍历元数据,查找包含sa_type的项 for item in metadata_items: # 检查元数据项是否有__sqlmodel_sa_type__属性 if hasattr(item, '__sqlmodel_sa_type__'): return item.__sqlmodel_sa_type__ # 检查是否有__get_pydantic_core_schema__方法 if hasattr(item, '__get_pydantic_core_schema__'): try: # 调用该方法获取core schema schema = item.__get_pydantic_core_schema__( annotation, lambda x: None # 虚拟handler ) # 检查schema的metadata中是否有sa_type if isinstance(schema, dict) and 'metadata' in schema: sa_type = schema['metadata'].get('sa_type') if sa_type is not None: return sa_type except (TypeError, AttributeError, KeyError, ValueError): # Pydantic core schema获取可能失败: # - TypeError: 参数不匹配 # - AttributeError: metadata不存在 # - KeyError: schema结构不符合预期 # - ValueError: 无效的类型定义 # 这是正常的类型探测过程,继续检查下一个metadata项 pass # 方法3:检查类型本身是否有__get_pydantic_core_schema__ # (虽然NumpyVector已经在方法1处理,但这是通用的fallback) if hasattr(annotation, '__get_pydantic_core_schema__'): try: schema = annotation.__get_pydantic_core_schema__( annotation, lambda x: None # 虚拟handler ) if isinstance(schema, dict) and 'metadata' in schema: sa_type = schema['metadata'].get('sa_type') if sa_type is not None: return sa_type except (TypeError, AttributeError, KeyError, ValueError): # 类型本身的schema获取失败 # 这是正常的fallback机制,annotation可能不支持此协议 pass return None def _resolve_annotations(attrs: dict[str, Any]) -> tuple[ dict[str, Any], dict[str, str], Mapping[str, Any], Mapping[str, Any], ]: """ Resolve annotations from a class namespace with Python 3.14 (PEP 649) support. This helper prefers evaluated annotations (Format.VALUE) so that `typing.Annotated` metadata and custom types remain accessible. Forward references that cannot be evaluated are replaced with typing.ForwardRef placeholders to avoid aborting the whole resolution process. """ raw_annotations = attrs.get('__annotations__') or {} try: base_annotations = dict(raw_annotations) except TypeError: base_annotations = {} module_name = attrs.get('__module__') module_globals: dict[str, Any] if module_name and module_name in sys.modules: module_globals = dict(sys.modules[module_name].__dict__) else: module_globals = {} module_globals.setdefault('__builtins__', __builtins__) localns: dict[str, Any] = dict(attrs) try: temp_cls = type('AnnotationProxy', (object,), dict(attrs)) temp_cls.__module__ = module_name extras_kw = {'include_extras': True} if sys.version_info >= (3, 10) else {} evaluated = get_type_hints( temp_cls, globalns=module_globals, localns=localns, **extras_kw, ) except (NameError, AttributeError, TypeError, RecursionError): # get_type_hints可能失败的原因: # - NameError: 前向引用无法解析(类型尚未定义) # - AttributeError: 模块或类型不存在 # - TypeError: 无效的类型注解 # - RecursionError: 循环依赖的类型定义 # 这是正常情况,回退到原始注解字符串 evaluated = base_annotations return dict(evaluated), {}, module_globals, localns def _evaluate_annotation_from_string( field_name: str, annotation_strings: dict[str, str], current_type: Any, globalns: Mapping[str, Any], localns: Mapping[str, Any], ) -> Any: """ Attempt to re-evaluate the original annotation string for a field. This is used as a fallback when the resolved annotation lost its metadata (e.g., Annotated wrappers) and we need to recover custom sa_type data. """ if not annotation_strings: return current_type expr = annotation_strings.get(field_name) if not expr or not isinstance(expr, str): return current_type try: return eval(expr, globalns, localns) except (NameError, SyntaxError, AttributeError, TypeError): # eval可能失败的原因: # - NameError: 类型名称在namespace中不存在 # - SyntaxError: 注解字符串有语法错误 # - AttributeError: 访问不存在的模块属性 # - TypeError: 无效的类型表达式 # 这是正常的fallback机制,返回当前已解析的类型 return current_type class __DeclarativeMeta(SQLModelMetaclass): """ 一个智能的混合模式元类,它提供了灵活性和清晰度: 1. **自动设置 `table=True`**: 如果一个类继承了 `TableBaseMixin`,则自动应用 `table=True`。 2. **明确的字典参数**: 支持 `mapper_args={...}`, `table_args={...}`, `table_name='...'`。 3. **便捷的关键字参数**: 支持最常见的 mapper 参数作为顶级关键字(如 `polymorphic_on`)。 4. **智能合并**: 当字典和关键字同时提供时,会自动合并,且关键字参数有更高优先级。 """ _KNOWN_MAPPER_KEYS = { "polymorphic_on", "polymorphic_identity", "polymorphic_abstract", "version_id_col", "concrete", } def __new__(cls, name, bases, attrs, **kwargs): # 1. 约定优于配置:自动设置 table=True is_intended_as_table = any(getattr(b, '_has_table_mixin', False) for b in bases) if is_intended_as_table and 'table' not in kwargs: kwargs['table'] = True # 2. 智能合并 __mapper_args__ collected_mapper_args = {} # 首先,处理明确的 mapper_args 字典 (优先级较低) if 'mapper_args' in kwargs: collected_mapper_args.update(kwargs.pop('mapper_args')) # 其次,处理便捷的关键字参数 (优先级更高) for key in cls._KNOWN_MAPPER_KEYS: if key in kwargs: # .pop() 获取值并移除,避免传递给父类 collected_mapper_args[key] = kwargs.pop(key) # 如果收集到了任何 mapper 参数,则更新到类的属性中 if collected_mapper_args: existing = attrs.get('__mapper_args__', {}).copy() existing.update(collected_mapper_args) attrs['__mapper_args__'] = existing # 3. 处理其他明确的参数 if 'table_args' in kwargs: attrs['__table_args__'] = kwargs.pop('table_args') if 'table_name' in kwargs: attrs['__tablename__'] = kwargs.pop('table_name') if 'abstract' in kwargs: attrs['__abstract__'] = kwargs.pop('abstract') # 4. 从Annotated元数据中提取sa_type并注入到Field # 重要:必须在调用父类__new__之前处理,因为SQLModel会消费annotations # # Python 3.14兼容性问题: # - SQLModel在Python 3.14上会因为ClassVar[T]类型而崩溃(issubclass错误) # - 我们必须在SQLModel看到annotations之前过滤掉ClassVar字段 # - 虽然PEP 749建议不修改__annotations__,但这是修复SQLModel bug的必要措施 # # 获取annotations的策略: # - Python 3.14+: 优先从__annotate__获取(如果存在) # - fallback: 从__annotations__读取(如果存在) # - 最终fallback: 空字典 annotations, annotation_strings, eval_globals, eval_locals = _resolve_annotations(attrs) if annotations: attrs['__annotations__'] = annotations if annotationlib is not None: # 在Python 3.14中禁用descriptor,转为普通dict attrs['__annotate__'] = None for field_name, field_type in annotations.items(): field_type = _evaluate_annotation_from_string( field_name, annotation_strings, field_type, eval_globals, eval_locals, ) # 跳过字符串或ForwardRef类型注解,让SQLModel自己处理 if isinstance(field_type, str) or isinstance(field_type, typing.ForwardRef): continue # 跳过特殊类型的字段 origin = get_origin(field_type) # 跳过 ClassVar 字段 - 它们不是数据库字段 if origin is typing.ClassVar: continue # 跳过 Mapped 字段 - SQLAlchemy 2.0+ 的声明式字段,已经有 mapped_column if origin is Mapped: continue # 尝试从注解中提取sa_type sa_type = _extract_sa_type_from_annotation(field_type) if sa_type is not None: # 检查字段是否已有Field定义 field_value = attrs.get(field_name, Undefined) if field_value is Undefined: # 没有Field定义,创建一个新的Field并注入sa_type attrs[field_name] = Field(sa_type=sa_type) elif isinstance(field_value, FieldInfo): # 已有Field定义,检查是否已设置sa_type # 注意:只有在未设置时才注入,尊重显式配置 # SQLModel使用Undefined作为"未设置"的标记 if not hasattr(field_value, 'sa_type') or field_value.sa_type is Undefined: field_value.sa_type = sa_type # 如果field_value是其他类型(如默认值),不处理 # SQLModel会在后续处理中将其转换为Field # 5. 调用父类的 __new__ 方法,传入被清理过的 kwargs result = super().__new__(cls, name, bases, attrs, **kwargs) # 6. 修复:在联表继承场景下,继承父类的 __sqlmodel_relationships__ # SQLModel 为每个 table=True 的类创建新的空 __sqlmodel_relationships__ # 这导致子类丢失父类的关系定义,触发错误的 Column 创建 # 必须在 super().__new__() 之后修复,因为 SQLModel 会覆盖我们预设的值 if kwargs.get('table', False): for base in bases: if hasattr(base, '__sqlmodel_relationships__'): for rel_name, rel_info in base.__sqlmodel_relationships__.items(): # 只继承子类没有重新定义的关系 if rel_name not in result.__sqlmodel_relationships__: result.__sqlmodel_relationships__[rel_name] = rel_info # 同时修复被错误创建的 Column - 恢复为父类的 relationship if hasattr(base, rel_name): base_attr = getattr(base, rel_name) setattr(result, rel_name, base_attr) # 7. 检测:禁止子类重定义父类的 Relationship 字段 # 子类重定义同名的 Relationship 字段会导致 SQLAlchemy 关系映射混乱, # 应该在类定义时立即报错,而不是在运行时出现难以调试的问题。 for base in bases: parent_relationships = getattr(base, '__sqlmodel_relationships__', {}) for rel_name in parent_relationships: # 检查当前类是否在 attrs 中重新定义了这个关系字段 if rel_name in attrs: raise TypeError( f"类 {name} 不允许重定义父类 {base.__name__} 的 Relationship 字段 '{rel_name}'。" f"如需修改关系配置,请在父类中修改。" ) # 8. 修复:从 model_fields/__pydantic_fields__ 中移除 Relationship 字段 # SQLModel 0.0.27 bug:子类会错误地继承父类的 Relationship 字段到 model_fields # 这导致 Pydantic 尝试为 Relationship 字段生成 schema,因为类型是 # Mapped[list['Character']] 这种前向引用,Pydantic 无法解析, # 导致 __pydantic_complete__ = False # # 修复策略: # - 检查类的 __sqlmodel_relationships__ 属性 # - 从 model_fields 和 __pydantic_fields__ 中移除这些字段 # - Relationship 字段由 SQLAlchemy 管理,不需要 Pydantic 参与 relationships = getattr(result, '__sqlmodel_relationships__', {}) if relationships: model_fields = getattr(result, 'model_fields', {}) pydantic_fields = getattr(result, '__pydantic_fields__', {}) fields_removed = False for rel_name in relationships: if rel_name in model_fields: del model_fields[rel_name] fields_removed = True if rel_name in pydantic_fields: del pydantic_fields[rel_name] fields_removed = True # 如果移除了字段,重新构建 Pydantic 模式 # 注意:只在有字段被移除时才 rebuild,避免不必要的开销 if fields_removed and hasattr(result, 'model_rebuild'): result.model_rebuild(force=True) return result def __init__( cls, classname: str, bases: tuple[type, ...], dict_: dict[str, typing.Any], **kw: typing.Any, ) -> None: """ 重写 SQLModel 的 __init__ 以支持联表继承(Joined Table Inheritance) SQLModel 原始行为: - 如果任何基类是表模型,则不调用 DeclarativeMeta.__init__ - 这阻止了子类创建自己的表 修复逻辑: - 检测联表继承场景(子类有自己的 __tablename__ 且有外键指向父表) - 强制调用 DeclarativeMeta.__init__ 来创建子表 """ from sqlmodel.main import is_table_model_class, DeclarativeMeta, ModelMetaclass # 检查是否是表模型 if not is_table_model_class(cls): ModelMetaclass.__init__(cls, classname, bases, dict_, **kw) return # 检查是否有基类是表模型 base_is_table = any(is_table_model_class(base) for base in bases) if not base_is_table: # 没有基类是表模型,走正常的 SQLModel 流程 # 处理关系字段 cls._setup_relationships() DeclarativeMeta.__init__(cls, classname, bases, dict_, **kw) return # 关键:检测联表继承场景 # 条件: # 1. 当前类的 __tablename__ 与父类不同(表示需要新表) # 2. 当前类有字段带有 foreign_key 指向父表 current_tablename = getattr(cls, '__tablename__', None) # 查找父表信息 parent_table = None parent_tablename = None for base in bases: if is_table_model_class(base) and hasattr(base, '__tablename__'): parent_tablename = base.__tablename__ break # 检查是否有不同的 tablename has_different_tablename = ( current_tablename is not None and parent_tablename is not None and current_tablename != parent_tablename ) # 检查是否有外键字段指向父表的主键 # 注意:由于字段合并,我们需要检查直接基类的 model_fields # 而不是当前类的合并后的 model_fields has_fk_to_parent = False def _normalize_tablename(name: str) -> str: """标准化表名以进行比较(移除下划线,转小写)""" return name.replace('_', '').lower() def _fk_matches_parent(fk_str: str, parent_table: str) -> bool: """检查 FK 字符串是否指向父表""" if not fk_str or not parent_table: return False # FK 格式: "tablename.column" 或 "schema.tablename.column" parts = fk_str.split('.') if len(parts) >= 2: fk_table = parts[-2] # 取倒数第二个作为表名 # 标准化比较(处理下划线差异) return _normalize_tablename(fk_table) == _normalize_tablename(parent_table) return False if has_different_tablename and parent_tablename: # 首先检查当前类的 model_fields for field_name, field_info in cls.model_fields.items(): fk = getattr(field_info, 'foreign_key', None) if fk is not None and isinstance(fk, str) and _fk_matches_parent(fk, parent_tablename): has_fk_to_parent = True break # 如果没找到,检查直接基类的 model_fields(解决 mixin 字段被覆盖的问题) if not has_fk_to_parent: for base in bases: if hasattr(base, 'model_fields'): for field_name, field_info in base.model_fields.items(): fk = getattr(field_info, 'foreign_key', None) if fk is not None and isinstance(fk, str) and _fk_matches_parent(fk, parent_tablename): has_fk_to_parent = True break if has_fk_to_parent: break is_joined_inheritance = has_different_tablename and has_fk_to_parent if is_joined_inheritance: # 联表继承:需要创建子表 # 修复外键字段:由于字段合并,外键信息可能丢失 # 需要从基类的 mixin 中找回外键信息,并重建列 from sqlalchemy import Column, ForeignKey, inspect as sa_inspect from sqlalchemy.dialects.postgresql import UUID as SA_UUID from sqlalchemy.exc import NoInspectionAvailable from sqlalchemy.orm.attributes import InstrumentedAttribute # 联表继承:子表只应该有 id(FK 到父表)+ 子类特有的字段 # 所有继承自祖先表的列都不应该在子表中重复创建 # 收集整个继承链中所有祖先表的列名(这些列不应该在子表中重复) # 需要遍历整个 MRO,因为可能是多级继承(如 Tool -> Function -> GetWeatherFunction) ancestor_column_names: set[str] = set() for ancestor in cls.__mro__: if ancestor is cls: continue # 跳过当前类 if is_table_model_class(ancestor): try: # 使用 inspect() 获取 mapper 的公开属性 # 源码确认: mapper.local_table 是公开属性 (mapper.py:979-998) mapper = sa_inspect(ancestor) for col in mapper.local_table.columns: # 跳过 _polymorphic_name 列(鉴别器,由根父表管理) if col.name.startswith('_polymorphic'): continue ancestor_column_names.add(col.name) except NoInspectionAvailable: continue # 找到子类自己定义的字段(不在父类中的) child_own_fields: set[str] = set() for field_name in cls.model_fields: # 检查这个字段是否是在当前类直接定义的(不是继承的) # 通过检查父类是否有这个字段来判断 is_inherited = False for base in bases: if hasattr(base, 'model_fields') and field_name in base.model_fields: is_inherited = True break if not is_inherited: child_own_fields.add(field_name) # 从子类类属性中移除父表已有的列定义 # 这样 SQLAlchemy 就不会在子表中创建这些列 fk_field_name = None for base in bases: if hasattr(base, 'model_fields'): for field_name, field_info in base.model_fields.items(): fk = getattr(field_info, 'foreign_key', None) pk = getattr(field_info, 'primary_key', False) if fk is not None and isinstance(fk, str) and _fk_matches_parent(fk, parent_tablename): fk_field_name = field_name # 找到了外键字段,重建它 # 创建一个新的 Column 对象包含外键约束 new_col = Column( field_name, SA_UUID(as_uuid=True), ForeignKey(fk), primary_key=pk if pk else False ) setattr(cls, field_name, new_col) break else: continue break # 移除继承自祖先表的列属性(除了 FK/PK 和子类自己的字段) # 这防止 SQLAlchemy 在子表中创建重复列 # 注意:在 __init__ 阶段,列是 Column 对象,不是 InstrumentedAttribute for col_name in ancestor_column_names: if col_name == fk_field_name: continue # 保留 FK/PK 列(子表的主键,同时是父表的外键) if col_name == 'id': continue # id 会被 FK 字段覆盖 if col_name in child_own_fields: continue # 保留子类自己定义的字段 # 检查类属性是否是 Column 或 InstrumentedAttribute if col_name in cls.__dict__: attr = cls.__dict__[col_name] # Column 对象或 InstrumentedAttribute 都需要删除 if isinstance(attr, (Column, InstrumentedAttribute)): try: delattr(cls, col_name) except AttributeError: pass # 找到子类自己定义的关系(不在父类中的) # 继承的关系会从父类自动获取,只需要设置子类新增的关系 child_own_relationships: set[str] = set() for rel_name in cls.__sqlmodel_relationships__: is_inherited = False for base in bases: if hasattr(base, '__sqlmodel_relationships__') and rel_name in base.__sqlmodel_relationships__: is_inherited = True break if not is_inherited: child_own_relationships.add(rel_name) # 只为子类自己定义的新关系调用关系设置 if child_own_relationships: cls._setup_relationships(only_these=child_own_relationships) # 强制调用 DeclarativeMeta.__init__ DeclarativeMeta.__init__(cls, classname, bases, dict_, **kw) else: # 非联表继承:单表继承或正常 Pydantic 模型 ModelMetaclass.__init__(cls, classname, bases, dict_, **kw) def _setup_relationships(cls, only_these: set[str] | None = None) -> None: """ 设置 SQLAlchemy 关系字段(从 SQLModel 源码复制) Args: only_these: 如果提供,只设置这些关系(用于 joined table inheritance 子类) 如果为 None,设置所有关系(默认行为) """ from sqlalchemy.orm import relationship, Mapped from sqlalchemy import inspect from sqlmodel.main import get_relationship_to from typing import get_origin for rel_name, rel_info in cls.__sqlmodel_relationships__.items(): # 如果指定了 only_these,只设置这些关系 if only_these is not None and rel_name not in only_these: continue if rel_info.sa_relationship: setattr(cls, rel_name, rel_info.sa_relationship) continue raw_ann = cls.__annotations__[rel_name] origin: typing.Any = get_origin(raw_ann) if origin is Mapped: ann = raw_ann.__args__[0] else: ann = raw_ann cls.__annotations__[rel_name] = Mapped[ann] relationship_to = get_relationship_to( name=rel_name, rel_info=rel_info, annotation=ann ) rel_kwargs: dict[str, typing.Any] = {} if rel_info.back_populates: rel_kwargs["back_populates"] = rel_info.back_populates if rel_info.cascade_delete: rel_kwargs["cascade"] = "all, delete-orphan" if rel_info.passive_deletes: rel_kwargs["passive_deletes"] = rel_info.passive_deletes if rel_info.link_model: ins = inspect(rel_info.link_model) local_table = getattr(ins, "local_table") if local_table is None: raise RuntimeError( f"Couldn't find secondary table for {rel_info.link_model}" ) rel_kwargs["secondary"] = local_table rel_args: list[typing.Any] = [] if rel_info.sa_relationship_args: rel_args.extend(rel_info.sa_relationship_args) if rel_info.sa_relationship_kwargs: rel_kwargs.update(rel_info.sa_relationship_kwargs) rel_value = relationship(relationship_to, *rel_args, **rel_kwargs) setattr(cls, rel_name, rel_value) class SQLModelBase(SQLModel, metaclass=__DeclarativeMeta): """此类必须和TableBase系列类搭配使用""" model_config = ConfigDict(use_attribute_docstrings=True, validate_by_name=True)