Source code for codegen_database.plugins.check

"""Check constraint plugins for codegen_database dimensions.

:class:`TableCheckPlugin` converts
:class:`~codegen_database.check.CodegenDatabaseCheck`
items into real SQLAlchemy ``CheckConstraint`` objects on a table
(for simple and append-only dimensions).
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from sqlalchemy import CheckConstraint

from codegen_database.check import CodegenDatabaseCheck, collect_checks
from codegen_database.plugin import Dynamic, Plugin, requires
from codegen_database.validation import validate_column_references

if TYPE_CHECKING:
    from codegen_database.factory.context import FactoryContext


@requires(Dynamic("table_key"))
class _CheckPlugin(Plugin):
    """Base class for check-constraint enforcement plugins.

    Handles the shared logic of collecting
    :class:`~codegen_database.check.CodegenDatabaseCheck` items,
    performing an early exit when there are none, and validating that
    each check only
    references known columns.  Concrete subclasses supply the column
    name set and the application logic.

    """

    def __init__(self, table_key: str = "primary") -> None:
        """Store the context key."""
        self.table_key = table_key

    def _column_names(self, ctx: FactoryContext) -> set[str]:
        """Return the set of column names available for validation.

        Args:
            ctx: The active factory context.

        Returns:
            Set of known column names for this plugin's target.

        """
        raise NotImplementedError

    def _apply(
        self,
        ctx: FactoryContext,
        checks: list[CodegenDatabaseCheck],
    ) -> None:
        """Apply validated checks to the target.

        Args:
            ctx: The active factory context.
            checks: Validated
                :class:`~codegen_database.check.CodegenDatabaseCheck`
                items to apply.

        """
        raise NotImplementedError

    def run(self, ctx: FactoryContext) -> None:
        """Collect, validate, and apply checks."""
        checks = collect_checks(ctx.schema_items)

        if not checks:
            return

        col_names = self._column_names(ctx)

        for cave_check in checks:
            validate_column_references(
                f"CodegenDatabaseCheck {cave_check.name!r}",
                cave_check.column_names(),
                col_names,
            )

        self._apply(ctx, checks)


[docs] class TableCheckPlugin(_CheckPlugin): """Materialize check items as table constraints. Turns :class:`~codegen_database.check.CodegenDatabaseCheck` items into SQLAlchemy ``CheckConstraint`` objects. Reads ``CodegenDatabaseCheck`` items from ``ctx.schema_items``, resolves ``{col}`` markers to plain column names (identity), and appends real ``CheckConstraint`` objects to the target table. Args: table_key: Key in ``ctx`` for the target table (default ``"primary"``). """ def _column_names(self, ctx: FactoryContext) -> set[str]: """Return column names from the physical table.""" return {c.name for c in ctx[self.table_key].columns} def _apply( self, ctx: FactoryContext, checks: list[CodegenDatabaseCheck], ) -> None: """Append SQLAlchemy CheckConstraints to the table.""" table = ctx[self.table_key] for cave_check in checks: expr = cave_check.resolve(lambda c: c) constraint = CheckConstraint(expr, name=cave_check.name) table.append_constraint(constraint)