I wanna write a nice TimestampMixin
class for my project, I hope it can:
created_at
: autonow when creation, and never change since thatupdated_at
: autonow when creationupdated_at
: auto update updated_at
if it is not specified when update.like:
foo = Foo(name='foo') # created_at, updated_at autofilled
time.sleep(3)
foo.name = 'bar' # `updated_at` auto updated!
data_dict = {"name": "bar", "created_at": 1717288789, "updated_at": 1717288801}
foo_from_orm = Foo.model_validate(**data_dict) # `created_at`: 1717288789, `updated_at`: 1717288801
For now, I have no solution, I can only manually write a on_update
function and manually call it everytime when I update the model.
Is there any better solution or any ideas on this issue?
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()
Pydantic v2 answer:
You can use validate_assignment from ConfigDict
to force model validation after updating your model. Notice that validate_assignment
is False by default.
The only catch is that you need to turn off the validate_assignment
before you update any field to prevent an infinite loop:
@pydantic.model_validator(mode="after")
@classmethod
def set_updated_at(cls, obj):
obj.model_config["validate_assignment"] = False
obj.updated_at = now_factory()
obj.model_config["validate_assignment"] = True
return obj
So in your case, here is a working example:
now_factory = lambda: int(datetime.now(UTC).timestamp())
class TimestampMixin(pydantic.BaseModel):
model_config = pydantic.ConfigDict(
validate_assignment=True,
)
created_at: int = pydantic.Field(
default_factory=now_factory
)
updated_at: int | None = pydantic.Field(None)
@pydantic.model_validator(mode="after")
@classmethod
def set_updated_at(cls, obj):
obj.model_config["validate_assignment"] = False
obj.updated_at = now_factory()
obj.model_config["validate_assignment"] = True
return obj
class User(TimestampMixin, pydantic.BaseModel):
name: str | None = pydantic.Field(None)
age: int | None = pydantic.Field(None)
if __name__ == "__main__":
# create your object
user = User()
print(user.model_dump()) # {'created_at': 1717414213, 'updated_at': 1717414213, 'name': None, 'age': None}
time.sleep(2)
user.name = "John"
print(user.model_dump()) # {'created_at': 1717414213, 'updated_at': 1717414215, 'name': 'John', 'age': None}
time.sleep(2)
user.age = 40
print(user.model_dump()) # {'created_at': 1717414213, 'updated_at': 1717414217, 'name': 'John', 'age': 40}