Skip to content

InsertStmtBuilder

Base and Concrete implementation

from abc import ABC, abstractmethod
from collections.abc import Sequence
from typing import Final, final

from sqlalchemy import Insert, insert
from sqlalchemy.orm import DeclarativeBase

from .dto import HistoryCreateDTO


class BaseInsertStmtBuilder(ABC):  # pragma: no cover
    """
    Abstract class for building SQL INSERT statements for history models.

    This class provides basic functionality for validating the presence of an
    entity ID field and serves as a foundation for specific implementations of
    insert statement builders.

    Attributes:
        _entity_id_field (Final[str]): The name of the entity ID field that must be
                                   defined in the history model.

    Args:
        history_model (type[DeclarativeBase]): The class of the history model that
                                                must contain the entity ID field.

    Raises:
        ValueError: If the entity ID field is not defined in the model.

    Example usage:
    ```
    class HistoryChanges(BaseHistoryChanges, Base):
        __tablename__ = "history_changes"

        user_id: Mapped[UUID | None] = mapped_column(
            ForeignKey("user.id", ondelete="SET NULL")
        )
        entity_id: Mapped[UUID] = mapped_column(index=True)

    ...


    @event.listens_for(Note, "after_update")
    def create_note_history(
        mapper: Mapper,
        connection: Connection,
        target: Note,
    ) -> None:
        stmt_builder = InsertStmtBuilder(history_model=HistoryChanges)
        ...
    ```
    """

    _entity_id_field: Final = "entity_id"

    def __init__(self, history_model: type[DeclarativeBase]) -> None:
        """
        Initializes BaseInsertStmtBuilder with the specified history model.

        Args:
            history_model (type[DeclarativeBase]): The class of the history model.

        Raises:
            ValueError: If the model does not contain the entity ID field.
        """

        self._validate_model(history_model)
        self._history_model = history_model

    def _validate_model(self, cls: type[DeclarativeBase]) -> None:
        """
        Validates that the model contains the specified entity ID field.

        Args:
            cls (type[DeclarativeBase]): The class of the history model to validate.

        Raises:
            ValueError: If the entity ID field is not defined in the model.
        """

        if not hasattr(cls, self._entity_id_field):
            msg = f'Define "{self._entity_id_field}" field in {cls.__qualname__}'
            raise ValueError(msg)

    @abstractmethod
    def build(
        self,
        dtos: Sequence[HistoryCreateDTO],
    ) -> Insert:
        """
        Build an INSERT statement based on the provided DTOs.

        Args:
            dtos (Sequence[HistoryCreateDTO]): A list of DTO objects containing the
                                                data to insert.

        Returns:
            Insert: An INSERT statement for execution in the database.
        """


@final
class InsertStmtBuilder(BaseInsertStmtBuilder):
    def build(
        self,
        dtos: Sequence[HistoryCreateDTO],
    ) -> Insert:
        return insert(self._history_model).values([dto.to_dict() for dto in dtos])

Example

class HistoryChanges(BaseHistoryChanges, Base):
    __tablename__ = "history_changes"

    user_id: Mapped[UUID | None] = mapped_column(
        ForeignKey("user.id", ondelete="SET NULL")
    )
    entity_id: Mapped[UUID] = mapped_column(index=True)

...


class Note(Base):
    __tablename__ = "note"

    id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid7)
    is_active: Mapped[bool] = mapped_column(default=True)
    name: Mapped[str] = mapped_column(String(255))
    description: Mapped[str] = mapped_column(String(512))


@event.listens_for(Note, "after_update")
def create_note_history(
    mapper: Mapper,
    connection: Connection,
    target: Note,
) -> None:
    stmt_builder = InsertStmtBuilder(history_model=HistoryChanges)
    ...