beanie icon indicating copy to clipboard operation
beanie copied to clipboard

Timestamp Mixin for auto-populated created_at, updated_at

Open roman-right opened this issue 3 years ago • 5 comments

Discussed in https://github.com/roman-right/beanie/discussions/514

Originally posted by slavovthinks March 25, 2023 Hey, folks 👋 Here is a Timestamp Mixin I've created for Beanie Documents. I'm wondering should I create a PR for it to be included as part of the project itself 🤔

from typing import Optional
from datetime import datetime

from pydantic import BaseModel, Field
from beanie import Document, before_event, Insert, Update, SaveChanges, Replace


class TimestampMixin(BaseModel):
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: Optional[datetime] = None

    @before_event(Insert)
    def set_created_at(self):
        self.created_at = datetime.utcnow()

    @before_event(Update, SaveChanges, Replace)
    def set_updated_at(self):
        self.updated_at = datetime.utcnow()
        
class ExampleDocument(TimestampMixin, Document):
    ...

```</div>

roman-right avatar Apr 13 '23 19:04 roman-right

This stopped working recently

humbertogontijo avatar Nov 22 '23 23:11 humbertogontijo

The created_at is working for me, but not the updated_at After adding a few logs in the hook, I see that the value of updated_at is getting set correctly, however it's not being updated to the database!

Kinjalrk2k avatar Dec 22 '23 08:12 Kinjalrk2k

I'm using this

from datetime import datetime
from typing import Any

from beanie import Document, Insert, Replace, SaveChanges, Update, WriteRules, before_event
from pydantic import Field
from pymongo.client_session import ClientSession


class BaseDocument(Document):
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime = Field(default_factory=datetime.utcnow)

    @before_event(Insert)
    def set_created_at(self) -> None:
        self.created_at = datetime.utcnow()

    @before_event(Update, SaveChanges, Replace)
    def set_updated_at(self) -> None:
        self.updated_at = datetime.utcnow()

    async def save(
        self,
        session: ClientSession | None = None,
        link_rule: WriteRules = WriteRules.DO_NOTHING,
        ignore_revision: bool = False,
        **kwargs: Any
    ) -> None:
        self.updated_at = datetime.utcnow()
        return await super().save(session, link_rule, ignore_revision, **kwargs)

Overriding the save was because before_event save wasnt reliable

humbertogontijo avatar Feb 17 '24 17:02 humbertogontijo

Instead of .save() I used .insert() and it seems to work that way!

Example:

new_user = User(...) # Like invoking the constructor of the model class
new_user.insert()
new_user.fetch_all_links() # (optional)

Kinjalrk2k avatar Feb 19 '24 13:02 Kinjalrk2k

I modified what @roman-right did, added Save event as well and doc.save() updates the date. Without that the date was not updated as indicated by @humbertogontijo. But i didnt have to ovverride the save method.

@before_event(Update, SaveChanges, Save, Replace)
 def set_updated_at(self):
     self.updated_at = naive_utcnow()

francisdeh avatar Feb 22 '24 09:02 francisdeh