Pydantic V2:如何实现更智能的created_at、updated_at字段
需求:实现自动维护时间戳的
TimestampMixin类 需要实现一个满足以下要求的TimestampMixin类:
created_at:创建时自动生成,生成后不可修改updated_at:创建时自动生成updated_at:更新模型字段且未指定该值时,自动更新
示例场景
foo = Foo(name='foo') # created_at、updated_at自动填充 time.sleep(3) foo.name = 'bar' # `updated_at`自动更新! data_dict = {"name": "bar", "created_at": 1717288789, "updated_at": 1717288801} foo_from_orm = Foo.model_validate(**data_dict) # `created_at`: 1717288789, `updated_at`: 1717288801
当前实现需手动调用on_update方法,无法自动触发更新,代码如下:
from datetime import datetime, UTC from pydantic import BaseModel, Field now_factory = lambda: int(datetime.now(UTC).timestamp()) class TimestampMixin(BaseModel): created_at: int = Field(default_factory=now_factory) updated_at: int = Field(default_factory=now_factory) def on_update(self): self.updated_at = now_factory()
解决方案
方案1:重写__setattr__实现自动更新与字段保护
利用Python的属性赋值拦截机制,自动更新updated_at并禁止修改created_at,完全适配Pydantic模型的使用逻辑:
from datetime import datetime, UTC from pydantic import BaseModel, Field now_factory = lambda: int(datetime.now(UTC).timestamp()) class TimestampMixin(BaseModel): created_at: int = Field(default_factory=now_factory) updated_at: int = Field(default_factory=now_factory) def __setattr__(self, name, value): # 禁止修改已生成的created_at if name == "created_at" and hasattr(self, "created_at"): raise ValueError("created_at字段不可修改") # 非updated_at字段赋值时,自动更新时间戳 if name != "updated_at": super().__setattr__("updated_at", now_factory()) super().__setattr__(name, value) # 测试用例 class Foo(TimestampMixin): name: str foo = Foo(name='foo') print(f"初始状态: created_at={foo.created_at}, updated_at={foo.updated_at}") import time time.sleep(1) foo.name = 'bar' print(f"更新后: created_at={foo.created_at}, updated_at={foo.updated_at}") # updated_at已自动更新 # 尝试修改created_at会触发报错 try: foo.created_at = 123456 except ValueError as e: print(e) # 输出: created_at字段不可修改 # 从字典验证模型,保留传入的时间戳 data_dict = {"name": "bar", "created_at": 1717288789, "updated_at": 1717288801} foo_from_orm = Foo.model_validate(**data_dict) print(f"从字典创建: created_at={foo_from_orm.created_at}, updated_at={foo_from_orm.updated_at}")
方案2:结合Pydantic验证器增强初始化控制
如果需要在模型验证阶段就对created_at做保护,可以搭配字段验证器使用:
from datetime import datetime, UTC from pydantic import BaseModel, Field, field_validator now_factory = lambda: int(datetime.now(UTC).timestamp()) class TimestampMixin(BaseModel): created_at: int = Field(default_factory=now_factory) updated_at: int = Field(default_factory=now_factory) @field_validator('created_at', mode='after') def protect_created_at(cls, v, values): # 仅允许初始化时设置created_at,后续修改通过__setattr__拦截 if 'created_at' in values.data: raise ValueError("created_at字段不可修改") return v def __setattr__(self, name, value): if name == "created_at" and hasattr(self, "created_at"): raise ValueError("created_at字段不可修改") if name != "updated_at": super().__setattr__("updated_at", now_factory()) super().__setattr__(name, value)
方案3:ORM场景适配(以SQLAlchemy为例)
如果和ORM框架配合使用,可以结合ORM的事件监听,在数据库会话提交时自动更新时间戳,兼顾Pydantic模型和数据库操作:
from sqlalchemy import event from sqlalchemy.orm import Session # 假设ORM模型继承自TimestampMixin和SQLAlchemy的Base class FooORM(TimestampMixin): __tablename__ = 'foos' id = Column(Integer, primary_key=True) name = Column(String) # 监听会话提交前的事件,自动更新脏数据的updated_at @event.listens_for(Session, 'before_flush') def update_timestamp(session, flush_context, instances): for instance in session.dirty: if isinstance(instance, TimestampMixin): instance.updated_at = now_factory()
内容的提问来源于stack exchange,提问作者steveflyer
相关产品推荐
相关产品推荐

