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

如何正确创建Pydantic扩展模型?FastAPI+ES遇验证失败

问题:Pydantic继承抽象基类时的字段验证错误

我正在开发一个基于FastAPI和ElasticSearch的应用,希望通过抽象化的Pydantic BaseModel创建实体类,实现API层与索引层的互操作性。但在继承BaseIndex(继承自BaseModel的抽象基类)创建UserIndex子类时,实例化对象时触发验证错误,提示created_on、modified_on、created_by、modified_by字段缺失。

类层级代码

from abc import ABCMeta, abstractmethod
from datetime import datetime
from pydantic import BaseModel, field_serializer, field_validator

class BaseIndex(BaseModel, metaclass=ABCMeta):
    id: str
    name: str
    created_on: datetime
    modified_on: datetime
    created_by: str
    modified_by: str

    @field_serializer("created_on", "modified_on", mode="plain")
    @classmethod
    def serialize_datetime(cls, v: datetime):
        return int(v.timestamp())

    @field_validator("created_on", "modified_on", mode="after")
    @classmethod
    def validate_datetime(cls, v: int):
        return datetime.utcfromtimestamp(v)

    @abstractmethod
    def as_schema(self, with_defaults: bool = False):
        pass

    @classmethod
    def from_record(cls, record: dict[str, Any]):
        pass
# ----------------------------------------------------------- #
class UserIndex(BaseIndex):
    user_name: str
    email: str
    first_name: str
    last_name: str
    groups: list[str]
    roles: list[str]
    last_login: datetime

    @field_serializer("last_login", mode="plain")
    def serialize_last_login(cls, value: datetime):
        return int(value.timestamp())

    @field_validator("last_login", mode="after")
    def validate_last_login(cls, value: int):
        return datetime.utcfromtimestamp(value)

    def as_schema(self, with_defaults: bool = False):
        return {
            "properties": {
                "id": {"type": "keyword"},
                "name": {"type": "string"},
                "user_name": {"type": "keyword"},
                "email": {"type": "keyword"},
                "first_name": {"type": "string", "copy_to": "name"},
                "last_name": {"type": "string", "copy_to": "name"},
                "groups": {"type": "string"},
                "roles": {"type": "string"}
            },

        }

实例化代码

user = UserIndex(id="u1", name="A1", user_name="Bob", email="bob@bb.com", first_name="Bob", last_name="B", 
            groups=["g1", "g2"], roles=["r1", "r2"], last_login=datetime.now())

触发的验证错误

File ".....\Lib\site-packages\pydantic\main.py", line 150, in __init__
    __pydantic_self__.__pydantic_validator__.validate_python(data, self_instance=__pydantic_self__)
pydantic_core._pydantic_core.ValidationError: 4 validation errors for User
created_on
  Field required [type=missing, input_value={'id': 'u1..': 'A1'}, input_type=dict]
modified_on
  Field required [type=missing, input_value={'id': 'u1..': 'A1'}, input_type=dict]
created_by
  Field required [type=missing, input_value={'id': 'u1..': 'A1'}, input_type=dict]
modified_by
  Field required [type=missing, input_value={'id': 'u1..': 'A1'}, input_type=dict]

解决方案

1. 为BaseIndex的必填字段设置默认值

BaseIndex中的四个字段是必填定义,实例化子类时必须传入。可以通过Field的default_factory或直接设置默认值,实现自动填充:

from abc import ABCMeta, abstractmethod
from datetime import datetime
from pydantic import BaseModel, field_serializer, field_validator, Field
from typing import Any

class BaseIndex(BaseModel, metaclass=ABCMeta):
    id: str
    name: str
    # 用default_factory动态生成UTC时间,避免所有实例共享同一时间戳
    created_on: datetime = Field(default_factory=datetime.utcnow)
    modified_on: datetime = Field(default_factory=datetime.utcnow)
    # 根据业务场景设置默认值,示例用"system"
    created_by: str = Field(default="system")
    modified_by: str = Field(default="system")

    @field_serializer("created_on", "modified_on", mode="plain")
    @classmethod
    def serialize_datetime(cls, v: datetime):
        return int(v.timestamp())

    @field_validator("created_on", "modified_on", mode="before")
    @classmethod
    def validate_datetime(cls, v):
        # 兼容输入为时间戳的场景,先转换为datetime再验证
        if isinstance(v, int):
            return datetime.utcfromtimestamp(v)
        return v

    @abstractmethod
    def as_schema(self, with_defaults: bool = False):
        pass

    @classmethod
    def from_record(cls, record: dict[str, Any]):
        # 实现从ES记录转换为模型的逻辑,自动处理时间戳字段
        record_copy = record.copy()
        time_fields = ["created_on", "modified_on", "last_login"]
        for field in time_fields:
            if field in record_copy and isinstance(record_copy[field], int):
                record_copy[field] = datetime.utcfromtimestamp(record_copy[field])
        return cls(**record_copy)

2. 修正子类的序列化/验证方法

UserIndex中的序列化方法应为实例方法(参数用self),验证方法需添加@classmethod装饰器,并将mode改为before(确保在字段验证前完成类型转换):

class UserIndex(BaseIndex):
    user_name: str
    email: str
    first_name: str
    last_name: str
    groups: list[str]
    roles: list[str]
    last_login: datetime = Field(default_factory=datetime.utcnow)

    @field_serializer("last_login", mode="plain")
    def serialize_last_login(self, value: datetime):
        return int(value.timestamp())

    @field_validator("last_login", mode="before")
    @classmethod
    def validate_last_login(cls, value):
        if isinstance(value, int):
            return datetime.utcfromtimestamp(value)
        return value

    def as_schema(self, with_defaults: bool = False):
        return {
            "properties": {
                "id": {"type": "keyword"},
                "name": {"type": "string"},
                "user_name": {"type": "keyword"},
                "email": {"type": "keyword"},
                "first_name": {"type": "string", "copy_to": "name"},
                "last_name": {"type": "string", "copy_to": "name"},
                "groups": {"type": "string"},
                "roles": {"type": "string"},
                "created_on": {"type": "date", "format": "epoch_second"},
                "modified_on": {"type": "date", "format": "epoch_second"},
                "last_login": {"type": "date", "format": "epoch_second"}
            },
        }

3. 实例化子类

修改后,实例化时无需手动传入四个基础字段,可直接使用默认值,也可按需覆盖:

from datetime import datetime

user = UserIndex(
    id="u1",
    name="A1",
    user_name="Bob",
    email="bob@bb.com",
    first_name="Bob",
    last_name="B",
    groups=["g1", "g2"],
    roles=["r1", "r2"],
    # 可选:手动指定last_login,否则用默认的当前UTC时间
    last_login=datetime.now()
)

补充:动态填充用户字段(FastAPI场景)

如果created_by/modified_by需要使用当前登录用户,可结合FastAPI的依赖注入实现:

from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)):
    # 实现获取当前登录用户的逻辑
    return {"username": "current_user"}

@app.post("/users/")
async def create_user(
    user_data: UserIndex,
    current_user = Depends(get_current_user)
):
    # 覆盖默认的创建/修改人字段
    user_data.created_by = current_user["username"]
    user_data.modified_by = current_user["username"]
    # 后续逻辑:将数据同步到ElasticSearch
    return user_data

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 16:09:50