from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field, SecretStr AddressBookScope = Literal["user", "group", "tenant", "system"] AddressSyncDirection = Literal["read_only", "import", "export", "two_way"] AddressSyncStatus = Literal["idle", "running", "succeeded", "failed", "conflict", "disabled"] AddressSyncDiagnosticSeverity = Literal["debug", "info", "warning", "error"] AddressSyncConflictStatus = Literal["open", "resolved", "ignored"] AddressSyncConflictResolution = Literal["keep_local", "use_remote", "merge", "manual", "ignored"] AddressCardDavAuthType = Literal["none", "basic", "bearer"] AddressSyncPlanAction = Literal["create", "update", "delete", "remote_create", "remote_update", "remote_delete", "conflict", "unchanged", "error"] class ContactEmailPayload(BaseModel): label: str | None = Field(default=None, max_length=80) email: str = Field(max_length=320) is_primary: bool = False class ContactPhonePayload(BaseModel): label: str | None = Field(default=None, max_length=80) phone: str = Field(max_length=100) is_primary: bool = False class ContactPostalAddressPayload(BaseModel): label: str | None = Field(default=None, max_length=80) street: str | None = Field(default=None, max_length=500) postal_code: str | None = Field(default=None, max_length=40) locality: str | None = Field(default=None, max_length=255) region: str | None = Field(default=None, max_length=255) country: str | None = Field(default=None, max_length=255) is_primary: bool = False class AddressBookCreateRequest(BaseModel): scope_type: AddressBookScope = "user" group_id: str | None = Field(default=None, max_length=36) name: str = Field(min_length=1, max_length=255) description: str | None = None class AddressBookUpdateRequest(BaseModel): name: str | None = Field(default=None, min_length=1, max_length=255) description: str | None = None class AddressBookResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str tenant_id: str | None = None scope_type: AddressBookScope scope_id: str | None = None name: str description: str | None = None source_kind: str source_ref: str | None = None read_only: bool sync_status: str | None = None sync_error: str | None = None contact_count: int = 0 deleted_at: datetime | None = None created_at: datetime updated_at: datetime class AddressBookListResponse(BaseModel): address_books: list[AddressBookResponse] class AddressListCreateRequest(BaseModel): name: str = Field(min_length=1, max_length=255) description: str | None = None class AddressListUpdateRequest(BaseModel): name: str | None = Field(default=None, min_length=1, max_length=255) description: str | None = None class AddressListEntryCreateRequest(BaseModel): contact_id: str = Field(max_length=36) contact_email_id: str | None = Field(default=None, max_length=36) contact_postal_address_id: str | None = Field(default=None, max_length=36) label: str | None = Field(default=None, max_length=255) class AddressListResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str tenant_id: str | None = None address_book_id: str name: str description: str | None = None source_kind: str source_ref: str | None = None read_only: bool entry_count: int = 0 deleted_at: datetime | None = None created_at: datetime updated_at: datetime class AddressListListResponse(BaseModel): address_lists: list[AddressListResponse] class AddressListEntryResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str address_list_id: str contact_id: str contact_email_id: str | None = None contact_postal_address_id: str | None = None target_kind: str label: str | None = None order_index: int contact_display_name: str email: str | None = None postal_address: str | None = None created_at: datetime updated_at: datetime class AddressListEntryListResponse(BaseModel): entries: list[AddressListEntryResponse] class ContactCreateRequest(BaseModel): display_name: str | None = Field(default=None, max_length=255) given_name: str | None = Field(default=None, max_length=255) family_name: str | None = Field(default=None, max_length=255) organization: str | None = Field(default=None, max_length=255) role_title: str | None = Field(default=None, max_length=255) note: str | None = None tags: list[str] = Field(default_factory=list) emails: list[ContactEmailPayload] = Field(default_factory=list) phones: list[ContactPhonePayload] = Field(default_factory=list) postal_addresses: list[ContactPostalAddressPayload] = Field(default_factory=list) provenance: dict[str, Any] = Field(default_factory=dict) class ContactUpdateRequest(BaseModel): display_name: str | None = Field(default=None, max_length=255) given_name: str | None = Field(default=None, max_length=255) family_name: str | None = Field(default=None, max_length=255) organization: str | None = Field(default=None, max_length=255) role_title: str | None = Field(default=None, max_length=255) note: str | None = None tags: list[str] | None = None emails: list[ContactEmailPayload] | None = None phones: list[ContactPhonePayload] | None = None postal_addresses: list[ContactPostalAddressPayload] | None = None provenance: dict[str, Any] | None = None class ContactEmailResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str label: str | None = None email: str is_primary: bool class ContactPhoneResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str label: str | None = None phone: str is_primary: bool class ContactPostalAddressResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str label: str | None = None street: str | None = None postal_code: str | None = None locality: str | None = None region: str | None = None country: str | None = None is_primary: bool class ContactResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str tenant_id: str | None = None address_book_id: str display_name: str given_name: str | None = None family_name: str | None = None organization: str | None = None role_title: str | None = None note: str | None = None tags: list[str] source_kind: str source_ref: str | None = None source_payload_kind: str | None = None source_revision: str | None = None provenance: dict[str, Any] emails: list[ContactEmailResponse] phones: list[ContactPhoneResponse] postal_addresses: list[ContactPostalAddressResponse] deleted_at: datetime | None = None created_at: datetime updated_at: datetime class ContactListResponse(BaseModel): contacts: list[ContactResponse] class AddressLookupResponse(BaseModel): contacts: list[ContactResponse] class AddressBookWriteDecisionResponse(BaseModel): address_book_id: str address_book_label: str | None = None operation: str allowed: bool reason: str message: str scope_type: AddressBookScope | None = None scope_id: str | None = None tenant_id: str | None = None source_kind: str | None = None read_only: bool = False required_scopes: list[str] = Field(default_factory=list) provenance: dict[str, Any] = Field(default_factory=dict) class AddressBookWriteTargetsResponse(BaseModel): targets: list[AddressBookWriteDecisionResponse] = Field(default_factory=list) class AddressSyncSourceCreateRequest(BaseModel): connector_type: str = Field(min_length=1, max_length=60) display_name: str = Field(min_length=1, max_length=255) external_account_ref: str | None = Field(default=None, max_length=1000) external_address_book_ref: str | None = Field(default=None, max_length=1000) sync_direction: AddressSyncDirection = "read_only" read_only: bool | None = None enabled: bool = True sync_token: str | None = None etag: str | None = Field(default=None, max_length=1000) remote_revision: str | None = Field(default=None, max_length=1000) metadata: dict[str, Any] = Field(default_factory=dict) class AddressSyncSourceUpdateRequest(BaseModel): display_name: str | None = Field(default=None, min_length=1, max_length=255) external_account_ref: str | None = Field(default=None, max_length=1000) external_address_book_ref: str | None = Field(default=None, max_length=1000) sync_direction: AddressSyncDirection | None = None read_only: bool | None = None enabled: bool | None = None sync_token: str | None = None etag: str | None = Field(default=None, max_length=1000) remote_revision: str | None = Field(default=None, max_length=1000) metadata: dict[str, Any] | None = None class AddressSyncAttemptFinishRequest(BaseModel): status: Literal["succeeded", "failed", "conflict"] sync_token: str | None = None etag: str | None = Field(default=None, max_length=1000) remote_revision: str | None = Field(default=None, max_length=1000) error: str | None = None diagnostic: dict[str, Any] | None = None class AddressSyncSourceResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str tenant_id: str | None = None address_book_id: str connector_type: str display_name: str external_account_ref: str | None = None external_address_book_ref: str | None = None sync_direction: str read_only: bool enabled: bool status: str sync_token: str | None = None etag: str | None = None remote_revision: str | None = None last_attempted_at: datetime | None = None last_success_at: datetime | None = None last_error: str | None = None last_diagnostic: dict[str, Any] | None = None metadata: dict[str, Any] = Field(default_factory=dict) created_at: datetime updated_at: datetime class AddressSyncSourceListResponse(BaseModel): sync_sources: list[AddressSyncSourceResponse] class AddressSyncDiagnosticCreateRequest(BaseModel): severity: AddressSyncDiagnosticSeverity = "info" code: str = Field(min_length=1, max_length=120) message: str = Field(min_length=1) details: dict[str, Any] = Field(default_factory=dict) class AddressSyncDiagnosticResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str tenant_id: str | None = None sync_source_id: str severity: str code: str message: str details: dict[str, Any] = Field(default_factory=dict) created_at: datetime updated_at: datetime class AddressSyncDiagnosticListResponse(BaseModel): diagnostics: list[AddressSyncDiagnosticResponse] class AddressSyncTombstoneCreateRequest(BaseModel): contact_id: str | None = Field(default=None, max_length=36) remote_uid: str | None = Field(default=None, max_length=1000) resource_href: str | None = Field(default=None, max_length=1000) local_deleted_at: datetime | None = None remote_deleted_at: datetime | None = None synced_at: datetime | None = None metadata: dict[str, Any] = Field(default_factory=dict) class AddressSyncTombstoneResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str tenant_id: str | None = None sync_source_id: str address_book_id: str contact_id: str | None = None remote_uid: str | None = None resource_href: str | None = None local_deleted_at: datetime | None = None remote_deleted_at: datetime | None = None synced_at: datetime | None = None metadata: dict[str, Any] = Field(default_factory=dict) created_at: datetime updated_at: datetime class AddressSyncTombstoneListResponse(BaseModel): tombstones: list[AddressSyncTombstoneResponse] class AddressSyncConflictCreateRequest(BaseModel): contact_id: str | None = Field(default=None, max_length=36) remote_uid: str | None = Field(default=None, max_length=1000) resource_href: str | None = Field(default=None, max_length=1000) field_path: str = Field(min_length=1, max_length=500) local_value: dict[str, Any] | None = None remote_value: dict[str, Any] | None = None local_updated_at: datetime | None = None remote_updated_at: datetime | None = None metadata: dict[str, Any] = Field(default_factory=dict) class AddressSyncConflictResolveRequest(BaseModel): resolution: AddressSyncConflictResolution status: Literal["resolved", "ignored"] = "resolved" merged_payload: ContactCreateRequest | None = None class AddressSyncConflictResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str tenant_id: str | None = None sync_source_id: str address_book_id: str contact_id: str | None = None remote_uid: str | None = None resource_href: str | None = None field_path: str local_value: dict[str, Any] | None = None remote_value: dict[str, Any] | None = None local_updated_at: datetime | None = None remote_updated_at: datetime | None = None status: str resolution: str | None = None resolved_at: datetime | None = None resolved_by_account_id: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) created_at: datetime updated_at: datetime class AddressSyncConflictListResponse(BaseModel): conflicts: list[AddressSyncConflictResponse] class AddressCardDavDiscoveryRequest(BaseModel): url: str = Field(min_length=1, max_length=2000) auth_type: AddressCardDavAuthType = "none" username: str | None = Field(default=None, max_length=320) password: SecretStr | None = None bearer_token: SecretStr | None = None credential_ref: str | None = Field(default=None, max_length=1000) source_id: str | None = Field(default=None, max_length=36) class AddressCardDavAddressBookResponse(BaseModel): collection_url: str href: str display_name: str | None = None description: str | None = None ctag: str | None = None sync_token: str | None = None class AddressCardDavDiscoveryResponse(BaseModel): address_books: list[AddressCardDavAddressBookResponse] class AddressCardDavSourceCreateRequest(BaseModel): collection_url: str = Field(min_length=1, max_length=2000) display_name: str | None = Field(default=None, max_length=255) auth_type: AddressCardDavAuthType = "none" username: str | None = Field(default=None, max_length=320) password: SecretStr | None = None bearer_token: SecretStr | None = None credential_ref: str | None = Field(default=None, max_length=1000) sync_direction: AddressSyncDirection = "read_only" read_only: bool | None = None sync_token: str | None = None etag: str | None = Field(default=None, max_length=1000) remote_revision: str | None = Field(default=None, max_length=1000) class AddressSyncRunRequest(BaseModel): force_full: bool = False password: SecretStr | None = None bearer_token: SecretStr | None = None class AddressSyncPlanStats(BaseModel): created: int = 0 updated: int = 0 deleted: int = 0 conflicts: int = 0 unchanged: int = 0 errors: int = 0 fetched: int = 0 full_sync: bool = False used_sync_token: bool = False sync_token: str | None = None etag: str | None = None remote_revision: str | None = None class AddressSyncPlanItemResponse(BaseModel): action: AddressSyncPlanAction href: str | None = None remote_uid: str | None = None contact_id: str | None = None display_name: str | None = None etag: str | None = None message: str | None = None class AddressSyncPlanResponse(BaseModel): sync_source: AddressSyncSourceResponse stats: AddressSyncPlanStats items: list[AddressSyncPlanItemResponse] class VCardImportRequest(BaseModel): content: str = Field(min_length=1) class VCardImportIssue(BaseModel): index: int message: str severity: Literal["warning", "error"] = "error" field: str | None = None line: int | None = None class VCardImportResponse(BaseModel): imported: int skipped: int contacts: list[ContactResponse] issues: list[VCardImportIssue] = Field(default_factory=list)