"""email_groups.is_dynamic — Cloud Identity 动态组标记。 Revision ID: 266 Revises: 265 Create Date: 2026-06-09 动态组(Cloud Identity dynamic groups)的成员由查询规则自动计算,员工 手动 ``members.delete`` 会被规则重新算回去 / 被 API 拒绝 —— 退了等于没退。 新增可空布尔列 ``is_dynamic`` 由 6h 的 ``google.group_sync`` 通过 Cloud Identity ``groups.list(view=FULL)`` 的 ``dynamicGroupMetadata`` 填充: * ``NULL`` —— 未知 / 从未判定过(服务账号尚未配 cloud-identity.groups .readonly scope 时优雅降级保持 NULL,不拦截、不影响同步)。 * ``0`` —— 静态组(可自助退出)。 * ``1`` —— 动态组(「我的邮件组」Tab 置灰退出 + 提示找 IT;leave 服务端 也加同名拦截兜底)。 幂等:``_column_exists`` 守卫。Downgrade 删列。 """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "266" down_revision = "265" branch_labels = None depends_on = None _TABLE = "email_groups" _COL = "is_dynamic" def _column_exists(table: str, column: str) -> bool: conn = op.get_bind() result = conn.execute( sa.text( "SELECT COUNT(*) FROM information_schema.columns " "WHERE table_schema = DATABASE() " "AND table_name = :t AND column_name = :c" ), {"t": table, "c": column}, ) return (result.scalar() or 0) > 0 def upgrade() -> None: if not _column_exists(_TABLE, _COL): op.add_column( _TABLE, sa.Column(_COL, sa.Boolean(), nullable=True), ) def downgrade() -> None: if _column_exists(_TABLE, _COL): op.drop_column(_TABLE, _COL)