BeanieODM / beanie

Asynchronous Python ODM for MongoDB
http://beanie-odm.dev/
Apache License 2.0
2.04k stars 215 forks source link

Timestamp Mixin for auto-populated created_at, updated_at #535

Open roman-right opened 1 year ago

roman-right commented 1 year ago

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 🤔 ```python 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): ... ```
humbertogontijo commented 11 months ago

This stopped working recently

Kinjalrk2k commented 10 months ago

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!

humbertogontijo commented 8 months ago

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

Kinjalrk2k commented 8 months ago

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)
francisdeh commented 7 months ago

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()