from __future__ import annotations import uuid from datetime import datetime from decimal import Decimal from typing import Any, Literal from pydantic import AliasChoices, BaseModel, ConfigDict, Field, field_validator class SendRequest(BaseModel): model_config = ConfigDict(extra="forbid", strict=True) idempotency_key: str = Field(min_length=8, max_length=192) template_code: Literal["auth_otp"] locale: Literal["ru"] phone_e164: str = Field(min_length=9, max_length=16) substitutions: dict[str, str | int] = Field(min_length=1, max_length=16) customer_ref: str = Field(min_length=1, max_length=128) message_ttl_sec: int = Field(ge=60, le=86400) class SendResponse(BaseModel): sms_message_id: uuid.UUID ordered_at: datetime class MessageResponse(BaseModel): sms_message_id: uuid.UUID ordered_at: datetime updated_at: datetime requester_service: str process: str channel: str provider: str phone_masked: str template_code: str customer_ref: str | None send_status: str delivery_status: str provider_message_id: str | None accepted_at: datetime | None sent_at: datetime | None delivered_at: datetime | None attempt_count: int provider_error_code: str | None class CallbackItem(BaseModel): model_config = ConfigDict(extra="allow") channel_type: str = Field(validation_alias=AliasChoices("channel_type", "channelType")) message_uuid: str = Field( min_length=1, max_length=128, validation_alias=AliasChoices("message_uuid", "messageUuid"), ) external_message_id: str = Field( min_length=1, max_length=128, validation_alias=AliasChoices("external_message_id", "externalMessageId"), ) callback_event: str = Field( min_length=1, max_length=32, validation_alias=AliasChoices("callback_event", "callbackEvent", "event"), ) status: str = Field(min_length=1, max_length=32) status_time: datetime = Field(validation_alias=AliasChoices("status_time", "statusTime")) error_code: str | None = Field( default=None, validation_alias=AliasChoices("error_code", "errorCode") ) parts: int | None = Field(default=None, ge=0) price: Decimal | None = Field(default=None, ge=0) currency: str | None = Field(default=None, min_length=3, max_length=3) @field_validator("status_time") @classmethod def require_timezone(cls, value: datetime) -> datetime: if value.tzinfo is None or value.utcoffset() is None: raise ValueError("status_time requires a timezone") return value class ErrorDetail(BaseModel): code: str message: str request_id: str details: dict[str, Any] | list[dict[str, Any]] class ErrorEnvelope(BaseModel): error: ErrorDetail