Source code for haive.core.common.mixins.timestamp_mixin
"""mixins.timestamps.=================Reusable timestamp mixins for Pydantic models.This module provides composable mixins for automatic creation and accesstimestamps in Pydantic models, with built-in UTC, field freezing, customserialization, and age calculation (as both seconds and human-readable string).Mixins: - CreatedTimestampMixin: Adds a frozen `created_at` datetime field. - AccessTimestampsMixin: Adds a frozen `last_accessed_at` datetime field, internal touch logic, and computed age fields.Typical usage example: class MyLog(CreatedTimestampMixin): event: str class MySession(AccessTimestampsMixin): user_id: int log = MyLog(event="example") session = MySession(user_id=42) print(log.created_at) # UTC datetime of creation print(session.age_human) # e.g. '0 minutes, 2 seconds'All datetime fields are timezone-aware (UTC).All serialization returns integer POSIX timestamps for compatibility.Intended for use with Sphinx AutoAPI and Google-style docstrings."""fromdatetimeimportUTC,datetime,timedeltafromtypingimportAny,SelffrompydanticimportBaseModel,Field,computed_field,field_serializer,model_validator
[docs]defutcnow()->datetime:"""Get the current UTC datetime with timezone information. Returns: datetime: The current UTC datetime. """returndatetime.now(UTC)
[docs]defto_int_timestamp(dt:datetime,_info:Any=None)->int:"""Convert a datetime object to an integer POSIX timestamp. Args: dt (datetime): The datetime to convert. _info (Any, optional): Not used, for Pydantic serializer compatibility. Returns: int: POSIX timestamp. """returnint(dt.timestamp())
[docs]classCreatedTimestampMixin(BaseModel):"""Mixin to provide a frozen, auto-populated UTC `created_at` timestamp field. Attributes: created_at (datetime): The UTC datetime when the object was created. """created_at:datetime=Field(default_factory=utcnow,frozen=True,description="UTC datetime when the object was created.",)@field_serializer("created_at")def_serialize_created(self,dt:datetime,_info:Any)->int:"""Serialize `created_at` as an integer timestamp. Args: dt (datetime): The datetime value. _info (Any): Pydantic serialization context. Returns: int: POSIX timestamp. """returnto_int_timestamp(dt,_info)@propertydefcreated_at_iso(self)->str:"""Get the ISO 8601 string of the creation time. Returns: str: The `created_at` timestamp as an ISO string. """returnself.created_at.isoformat()
[docs]classAccessTimestampsMixin(CreatedTimestampMixin):"""Mixin to add a frozen `last_accessed_at` timestamp, `touch` logic, and age calculation. Inherits: CreatedTimestampMixin: Provides `created_at`. Attributes: last_accessed_at (datetime): The UTC datetime when the object was last accessed. """last_accessed_at:datetime=Field(default_factory=utcnow,frozen=True,description="UTC datetime when the object was last accessed.",)@model_validator(mode="after")def_sync_last_accessed(self)->Self:"""Sync `last_accessed_at` to `created_at` immediately after model creation. Returns: AccessTimestampsMixin: The validated model instance. """object.__setattr__(self,"last_accessed_at",self.created_at)returnself@field_serializer("last_accessed_at")def_serialize_accessed(self,dt:datetime,_info:Any)->int:"""Serialize `last_accessed_at` as an integer timestamp. Args: dt (datetime): The datetime value. _info (Any): Pydantic serialization context. Returns: int: POSIX timestamp. """returnto_int_timestamp(dt,_info)def_touch(self)->None:"""Update the `last_accessed_at` timestamp to the current time. Only to be used by internal logic; bypasses frozen field restriction. """object.__setattr__(self,"last_accessed_at",utcnow())@propertydefage(self)->timedelta:"""Get the time since object creation as a `timedelta`. Returns: timedelta: The duration since `created_at`. """returnutcnow()-self.created_at@computed_field@propertydefage_seconds(self)->int:"""Get the object's age in seconds since creation. Returns: int: Age in seconds. """returnint(self.age.total_seconds())@propertydefage_human(self)->str:"""Get a human-readable string for the object's age. Returns: str: Age as 'X days, Y hours, Z minutes, N seconds'. """td=self.agemins,secs=divmod(td.total_seconds(),60)hours,mins=divmod(mins,60)days,hours=divmod(hours,24)parts=[]ifdays:parts.append(f"{int(days)} days")ifhours:parts.append(f"{int(hours)}")