Source code for codegen_database.ext.rls.alembic
"""Alembic autogenerate integration for RLS policies.
Call :func:`register_rls_alembic_events` in ``env.py`` alongside
:func:`~codegen_database.alembic.register.alembic_hook` to enable
RLS policy diffing in Alembic autogenerate::
from codegen_database.alembic.register import alembic_hook
from codegen_database.ext.rls.alembic import register_rls_alembic_events
alembic_hook()
register_rls_alembic_events()
The comparator queries ``pg_policies`` and emits ``op.execute(...)``
statements for any policies that need to be created or dropped.
"""
from typing import TYPE_CHECKING
from alembic.autogenerate.compare import comparators
from alembic.autogenerate.render import renderers
from codegen_database.ext.rls.base import RLSPolicies
from codegen_database.ext.rls.compare import (
CreateRLSPolicyOp,
DropRLSPolicyOp,
compare_rls_policies,
)
if TYPE_CHECKING:
from alembic.autogenerate.api import AutogenContext
from alembic.operations.ops import UpgradeOps
def _compare_rls(
autogen_context: AutogenContext,
upgrade_ops: UpgradeOps,
_schemas: object,
) -> None:
"""Compare desired RLS policies against the current database state."""
policies = RLSPolicies.extract(autogen_context.metadata)
if not policies:
return
assert autogen_context.connection # noqa: S101
result = compare_rls_policies(autogen_context.connection, policies)
upgrade_ops.ops.extend(result)
def _render_rls_op(
_autogen_context: AutogenContext,
op: CreateRLSPolicyOp | DropRLSPolicyOp,
) -> list[str]:
"""Render an RLS op as ``op.execute(...)`` statements."""
return [f'op.execute("""{cmd}""")' for cmd in op.to_sql()]
[docs]
def register_rls_alembic_events() -> None:
"""Register RLS comparator and renderer into Alembic.
Call once in ``env.py`` before ``context.configure()``.
"""
comparators.dispatch_for("schema")(_compare_rls)
renderers.dispatch_for(CreateRLSPolicyOp)(_render_rls_op)
renderers.dispatch_for(DropRLSPolicyOp)(_render_rls_op)