"""Update Mixin.""" from typing import Any from sqlalchemy import Connection, event from sqlalchemy.orm import Mapper from ..contexts import get_user_id from ..utils import current_timestamp, try_updatable class UpdateMixin: """Mixin for models with update tracking. AUTOMATIC BEHAVIOR on any model creation or change: - last_modified: Set to current timestamp in SYSTEM_TIMEZONE - last_modified_by: Set from ctx_user_id context To control automatic updates: ```python disable_on_update() enable_on_update() ``` The actual columns are defined in the model itself based on what exists in the database schema. A model using this mixin may have one or more of: - last_modified: When the record was last modified (including creation) - last_modified_by: Who made the last modification (including creation) """ def touch(self) -> None: """Manually update last_modified fields.""" self._on_update() def _on_update(self) -> None: """Update last_modified fields. Internal use only.""" try_updatable(self) if hasattr(self, 'last_modified'): self.last_modified = current_timestamp() if hasattr(self, 'last_modified_by'): self.last_modified_by = get_user_id() _on_update_enabled = True def disable_on_update() -> None: """Disable automatic updates of last_modified fields.""" global _on_update_enabled if not _on_update_enabled: return try: event.remove(UpdateMixin, 'before_update', _on_update) except Exception: pass try: event.remove(UpdateMixin, 'before_insert', _on_update) except Exception: pass _on_update_enabled = False def enable_on_update() -> None: """Enable automatic updates of last_modified fields.""" global _on_update_enabled if _on_update_enabled: return event.listen(UpdateMixin, 'before_update', _on_update, propagate=True) event.listen(UpdateMixin, 'before_insert', _on_update, propagate=True) _on_update_enabled = True def _on_update(mapper: Mapper, connection: Connection, target: Any) -> None: """Automatically update last_modified fields.""" target._on_update() # Register listeners at module import time event.listen(UpdateMixin, 'before_update', _on_update, propagate=True) event.listen(UpdateMixin, 'before_insert', _on_update, propagate=True)