Release v0.1.7

This commit is contained in:
2026-07-11 02:34:57 +02:00
parent c0afbc9333
commit 99560a74c6
5 changed files with 152 additions and 2 deletions

View File

@@ -10,7 +10,7 @@ readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
authors = [{ name = "GovOPlaN" }] authors = [{ name = "GovOPlaN" }]
dependencies = [ dependencies = [
"govoplan-core>=0.1.6", "govoplan-core>=0.1.7",
] ]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]

View File

@@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
from govoplan_core.core.identity import CAPABILITY_IDENTITY_DIRECTORY from govoplan_core.core.identity import CAPABILITY_IDENTITY_DIRECTORY
from govoplan_core.core.module_guards import persistent_table_uninstall_guard from govoplan_core.core.module_guards import persistent_table_uninstall_guard
@@ -58,7 +60,12 @@ manifest = ModuleManifest(
permissions=PERMISSIONS, permissions=PERMISSIONS,
role_templates=ROLE_TEMPLATES, role_templates=ROLE_TEMPLATES,
route_factory=_route_factory, route_factory=_route_factory,
migration_spec=MigrationSpec(module_id="identity", metadata=Base.metadata), migration_spec=MigrationSpec(
module_id="identity",
metadata=Base.metadata,
script_location=str(Path(__file__).with_name("migrations") / "versions"),
migration_after=("access",),
),
uninstall_guard_providers=( uninstall_guard_providers=(
persistent_table_uninstall_guard( persistent_table_uninstall_guard(
identity_models.Identity, identity_models.Identity,

View File

@@ -0,0 +1 @@
"""Identity module migrations."""

View File

@@ -0,0 +1,141 @@
"""identity directory
Revision ID: 5c6d7e8f9a10
Revises: 4a5b6c7d8e9f
Create Date: 2026-07-11 00:00:00.000000
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "5c6d7e8f9a10"
down_revision = "4a5b6c7d8e9f"
branch_labels = None
depends_on = None
def _tables() -> set[str]:
return set(sa.inspect(op.get_bind()).get_table_names())
def _indexes(table_name: str) -> set[str]:
if table_name not in _tables():
return set()
return {item["name"] for item in sa.inspect(op.get_bind()).get_indexes(table_name)}
def _create_index_if_missing(name: str, table_name: str, columns: list[str], *, unique: bool = False, **kwargs) -> None:
if table_name in _tables() and name not in _indexes(table_name):
op.create_index(name, table_name, columns, unique=unique, **kwargs)
def _drop_index_if_exists(name: str, table_name: str) -> None:
if table_name in _tables() and name in _indexes(table_name):
op.drop_index(name, table_name=table_name)
def upgrade() -> None:
tables = _tables()
if "identity_identities" not in tables:
op.create_table(
"identity_identities",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("display_name", sa.String(length=255), nullable=True),
sa.Column("external_subject", sa.String(length=255), nullable=True),
sa.Column("source", sa.String(length=50), nullable=False),
sa.Column("is_active", sa.Boolean(), nullable=False),
sa.Column("settings", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("pk_identity_identities")),
)
_create_index_if_missing(op.f("ix_identity_identities_external_subject"), "identity_identities", ["external_subject"])
tables = _tables()
if "identity_account_links" not in tables:
op.create_table(
"identity_account_links",
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("identity_id", sa.String(length=36), nullable=False),
sa.Column("account_id", sa.String(length=36), nullable=False),
sa.Column("is_primary", sa.Boolean(), nullable=False),
sa.Column("source", sa.String(length=50), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(
["identity_id"],
["identity_identities.id"],
name=op.f("fk_identity_account_links_identity_id_identity_identities"),
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_identity_account_links")),
sa.UniqueConstraint("identity_id", "account_id", name="uq_identity_module_account_links_identity_account"),
)
_create_index_if_missing(op.f("ix_identity_account_links_account_id"), "identity_account_links", ["account_id"])
_create_index_if_missing(op.f("ix_identity_account_links_identity_id"), "identity_account_links", ["identity_id"])
_create_index_if_missing(
"uq_identity_module_account_links_primary_account",
"identity_account_links",
["account_id"],
unique=True,
sqlite_where=sa.text("is_primary = 1"),
postgresql_where=sa.text("is_primary IS TRUE"),
)
_create_index_if_missing(
"uq_identity_module_account_links_primary_identity",
"identity_account_links",
["identity_id"],
unique=True,
sqlite_where=sa.text("is_primary = 1"),
postgresql_where=sa.text("is_primary IS TRUE"),
)
tables = _tables()
if {"access_identities", "access_identity_account_links"}.issubset(tables):
op.execute("""
INSERT INTO identity_identities
(id, display_name, external_subject, source, is_active, settings, created_at, updated_at)
SELECT id, display_name, external_subject, source, is_active, settings, created_at, updated_at
FROM access_identities source_identity
WHERE NOT EXISTS (
SELECT 1
FROM identity_identities target_identity
WHERE target_identity.id = source_identity.id
)
""")
op.execute("""
INSERT INTO identity_account_links
(id, identity_id, account_id, is_primary, source, created_at, updated_at)
SELECT id, identity_id, account_id, is_primary, source, created_at, updated_at
FROM access_identity_account_links source_link
WHERE EXISTS (
SELECT 1
FROM identity_identities target_identity
WHERE target_identity.id = source_link.identity_id
)
AND NOT EXISTS (
SELECT 1
FROM identity_account_links target_link
WHERE target_link.id = source_link.id
)
AND NOT EXISTS (
SELECT 1
FROM identity_account_links target_link
WHERE target_link.identity_id = source_link.identity_id
AND target_link.account_id = source_link.account_id
)
""")
def downgrade() -> None:
_drop_index_if_exists("uq_identity_module_account_links_primary_identity", "identity_account_links")
_drop_index_if_exists("uq_identity_module_account_links_primary_account", "identity_account_links")
_drop_index_if_exists(op.f("ix_identity_account_links_identity_id"), "identity_account_links")
_drop_index_if_exists(op.f("ix_identity_account_links_account_id"), "identity_account_links")
if "identity_account_links" in _tables():
op.drop_table("identity_account_links")
_drop_index_if_exists(op.f("ix_identity_identities_external_subject"), "identity_identities")
if "identity_identities" in _tables():
op.drop_table("identity_identities")

View File

@@ -0,0 +1 @@
"""Identity module migration versions."""