from __future__ import annotations from datetime import datetime from typing import Literal from pydantic import BaseModel, ConfigDict, Field, model_validator from govoplan_core.core.approvals import ApprovalActorSelector, ApprovalStepDefinition class CampaignApprovalSelectorInput(BaseModel): model_config = ConfigDict(extra="forbid") kind: Literal["account", "group", "role", "function_assignment", "any_account"] value: str = Field(min_length=1, max_length=255) label: str | None = Field(default=None, max_length=255) class CampaignApprovalStepInput(BaseModel): model_config = ConfigDict(extra="forbid") key: str = Field(min_length=1, max_length=120) label: str = Field(min_length=1, max_length=255) selectors: list[CampaignApprovalSelectorInput] = Field(min_length=1, max_length=500) required_approvals: int = Field(default=1, ge=1, le=500) rejection_policy: Literal["fail_fast", "collect"] = "fail_fast" due_at: datetime | None = None signature_required: bool = False forbidden_evidence_roles: list[ Literal["author", "owner", "validator", "builder", "reviewer"] ] = Field(default_factory=list, max_length=5) def to_definition(self) -> ApprovalStepDefinition: return ApprovalStepDefinition( key=self.key, label=self.label, selectors=tuple( ApprovalActorSelector(item.kind, item.value, item.label) for item in self.selectors ), required_approvals=self.required_approvals, rejection_policy=self.rejection_policy, due_at=self.due_at, signature_required=self.signature_required, forbidden_evidence_roles=tuple(self.forbidden_evidence_roles), ) class CampaignApprovalRequestInput(BaseModel): model_config = ConfigDict(extra="forbid") title: str = Field( default="Approve Campaign delivery", min_length=1, max_length=255 ) description: str | None = Field(default=None, max_length=10_000) steps: list[CampaignApprovalStepInput] = Field(default_factory=list, max_length=100) template_id: str | None = Field(default=None, max_length=36) template_revision: int | None = Field(default=None, ge=1) unique_actors_across_steps: bool = True expires_at: datetime | None = None policy_refs: list[str] = Field(default_factory=list, max_length=500) idempotency_key: str = Field(min_length=1, max_length=160) @model_validator(mode="after") def validate_source(self) -> "CampaignApprovalRequestInput": template = self.template_id is not None or self.template_revision is not None if template and (self.template_id is None or self.template_revision is None): raise ValueError("Template id and revision must be supplied together.") if template and self.steps: raise ValueError("Use either an Approval template or inline steps.") if not template and not self.steps: raise ValueError( "At least one Approval step is required without a template." ) return self __all__ = ["CampaignApprovalRequestInput"]