Skip to content

popoto.fields.datetime_field

popoto.fields.datetime_field

Datetime field for storing Python datetime objects in Redis.

This module provides DatetimeField, a specialized field type that extends the base Field class to handle datetime values with automatic timestamp management. It follows Django's familiar auto_now and auto_now_add patterns, making it intuitive for developers transitioning from Django ORM to Popoto's Redis-based persistence.

The DatetimeField is central to the Timestampable mixin pattern, enabling models to automatically track creation and modification times without manual intervention.

Example usage

class Article(popoto.Model): title = popoto.KeyField() published_at = popoto.DatetimeField(null=True) created_at = popoto.DatetimeField(auto_now_add=True) updated_at = popoto.DatetimeField(auto_now=True)

Design note: Unlike shortcuts.py fields (DateField, TimeField) which are simple type wrappers, DatetimeField lives in its own module because it introduces behavioral logic (auto_now/auto_now_add) beyond just type enforcement.

DatetimeField

Bases: Field

A field for storing datetime values with optional automatic timestamp management.

DatetimeField provides Django-compatible auto_now and auto_now_add functionality for Redis-persisted models. This design decision prioritizes developer familiarity over Redis-native timestamp patterns, reducing cognitive overhead when migrating from or working alongside Django projects.

The field automatically handles serialization through msgpack (via the parent Field class), storing datetime objects as ISO format strings in Redis.

Parameters:

Name Type Description Default
auto_now_add

Set to current datetime on first save only.

required
auto_now

Update to current datetime on every save.

required

Attributes:

Name Type Description
auto_now_add bool

When True, automatically sets the field to the current datetime on first save only. Useful for created_at timestamps.

auto_now bool

When True, automatically updates the field to the current datetime on every save. Useful for updated_at timestamps.

Example

Track when a user was created and last modified

class User(popoto.Model): username = popoto.KeyField() created_at = popoto.DatetimeField(auto_now_add=True) last_seen = popoto.DatetimeField(auto_now=True)

Or use the built-in Timestampable mixin for the common pattern

from popoto.utils.mixins import Timestampable

class User(Timestampable): username = popoto.KeyField()

Source code in src/popoto/fields/datetime_field.py
class DatetimeField(Field):
    """
    A field for storing datetime values with optional automatic timestamp management.

    DatetimeField provides Django-compatible auto_now and auto_now_add functionality
    for Redis-persisted models. This design decision prioritizes developer familiarity
    over Redis-native timestamp patterns, reducing cognitive overhead when migrating
    from or working alongside Django projects.

    The field automatically handles serialization through msgpack (via the parent
    Field class), storing datetime objects as ISO format strings in Redis.

    Args:
        auto_now_add: Set to current datetime on first save only.
        auto_now: Update to current datetime on every save.

    Attributes:
        auto_now_add (bool): When True, automatically sets the field to the current
            datetime on first save only. Useful for created_at timestamps.
        auto_now (bool): When True, automatically updates the field to the current
            datetime on every save. Useful for updated_at timestamps.

    Example:
        # Track when a user was created and last modified
        class User(popoto.Model):
            username = popoto.KeyField()
            created_at = popoto.DatetimeField(auto_now_add=True)
            last_seen = popoto.DatetimeField(auto_now=True)

        # Or use the built-in Timestampable mixin for the common pattern
        from popoto.utils.mixins import Timestampable

        class User(Timestampable):
            username = popoto.KeyField()
    """

    def __init__(self, *args, **kwargs):
        """
        Initialize a DatetimeField with optional automatic timestamp behavior.

        The auto_now_add and auto_now parameters are extracted before calling the
        parent constructor because they are DatetimeField-specific behaviors not
        recognized by the base Field class.

        Args:
            auto_now_add (bool, optional): If True, set to current datetime on
                creation only. Defaults to False.
            auto_now (bool, optional): If True, set to current datetime on every
                save. Defaults to False.
            **kwargs: Additional Field parameters (null, default, etc.).

        Note:
            Setting both auto_now_add=True and auto_now=True is redundant since
            auto_now will overwrite the value on every save including the first.
        """
        kwargs["type"] = datetime
        # Extract auto_now_add and auto_now before calling super
        self.auto_now_add = kwargs.pop("auto_now_add", False)
        self.auto_now = kwargs.pop("auto_now", False)
        super().__init__(*args, **kwargs)

    def format_value_pre_save(self, field_value, skip_auto_now=False, **kwargs):
        """
        Apply automatic timestamp logic before persisting to Redis.

        This method is called by the model's save pipeline, allowing DatetimeField
        to intercept and modify the value based on auto_now and auto_now_add settings.
        The hook pattern (format_value_pre_save) enables field-specific transformation
        logic without coupling to the model's save implementation.

        Args:
            field_value: The current datetime value, or None if not set.
            skip_auto_now: If True, suppress auto_now timestamp updates.
                Useful for data migrations where existing timestamps should
                be preserved. Does not affect auto_now_add behavior.
            **kwargs: Additional keyword arguments for forward compatibility.

        Returns:
            datetime: The processed datetime value. Returns current time for
                auto_now fields (unless skip_auto_now is True), current time
                for auto_now_add when field_value is falsy, or the original
                field_value otherwise.

        Implementation note:
            auto_now takes precedence if both flags are set, as it unconditionally
            returns datetime.now() regardless of existing value (unless skipped).
        """
        if self.auto_now_add and not field_value:
            return datetime.now()
        if self.auto_now and not skip_auto_now:
            return datetime.now()
        return field_value

format_value_pre_save(field_value, skip_auto_now=False, **kwargs)

Apply automatic timestamp logic before persisting to Redis.

This method is called by the model's save pipeline, allowing DatetimeField to intercept and modify the value based on auto_now and auto_now_add settings. The hook pattern (format_value_pre_save) enables field-specific transformation logic without coupling to the model's save implementation.

Parameters:

Name Type Description Default
field_value

The current datetime value, or None if not set.

required
skip_auto_now

If True, suppress auto_now timestamp updates. Useful for data migrations where existing timestamps should be preserved. Does not affect auto_now_add behavior.

False
**kwargs

Additional keyword arguments for forward compatibility.

{}

Returns:

Name Type Description
datetime

The processed datetime value. Returns current time for auto_now fields (unless skip_auto_now is True), current time for auto_now_add when field_value is falsy, or the original field_value otherwise.

Implementation note

auto_now takes precedence if both flags are set, as it unconditionally returns datetime.now() regardless of existing value (unless skipped).

Source code in src/popoto/fields/datetime_field.py
def format_value_pre_save(self, field_value, skip_auto_now=False, **kwargs):
    """
    Apply automatic timestamp logic before persisting to Redis.

    This method is called by the model's save pipeline, allowing DatetimeField
    to intercept and modify the value based on auto_now and auto_now_add settings.
    The hook pattern (format_value_pre_save) enables field-specific transformation
    logic without coupling to the model's save implementation.

    Args:
        field_value: The current datetime value, or None if not set.
        skip_auto_now: If True, suppress auto_now timestamp updates.
            Useful for data migrations where existing timestamps should
            be preserved. Does not affect auto_now_add behavior.
        **kwargs: Additional keyword arguments for forward compatibility.

    Returns:
        datetime: The processed datetime value. Returns current time for
            auto_now fields (unless skip_auto_now is True), current time
            for auto_now_add when field_value is falsy, or the original
            field_value otherwise.

    Implementation note:
        auto_now takes precedence if both flags are set, as it unconditionally
        returns datetime.now() regardless of existing value (unless skipped).
    """
    if self.auto_now_add and not field_value:
        return datetime.now()
    if self.auto_now and not skip_auto_now:
        return datetime.now()
    return field_value