from __future__ import annotations from enum import StrEnum from typing import Any from pydantic import BaseModel, ConfigDict, Field, model_validator class StrictModel(BaseModel): model_config = ConfigDict(extra="forbid", populate_by_name=True) class TransportSecurity(StrEnum): PLAIN = "plain" TLS = "tls" STARTTLS = "starttls" class TransportCredentials(StrictModel): username: str | None = None password: str | None = None class SmtpServerConfig(StrictModel): host: str | None = None port: int | None = Field(default=None, ge=1, le=65535) security: TransportSecurity = TransportSecurity.STARTTLS timeout_seconds: int = Field(default=30, ge=1) @model_validator(mode="after") def apply_default_port(self) -> "SmtpServerConfig": if self.port is None: self.port = default_smtp_port(self.security) return self class ImapServerConfig(StrictModel): host: str | None = None port: int | None = Field(default=None, ge=1, le=65535) security: TransportSecurity = TransportSecurity.TLS sent_folder: str = "auto" timeout_seconds: int = Field(default=30, ge=1) @model_validator(mode="before") @classmethod def discard_legacy_enabled(cls, value: Any) -> Any: if isinstance(value, dict) and "enabled" in value: data = dict(value) data.pop("enabled", None) return data return value @model_validator(mode="after") def apply_default_port(self) -> "ImapServerConfig": if self.port is None: self.port = default_imap_port(self.security) return self class SmtpConfig(SmtpServerConfig): username: str | None = None password: str | None = None class ImapConfig(ImapServerConfig): username: str | None = None password: str | None = None def default_smtp_port(security: TransportSecurity | str | None) -> int: if security == TransportSecurity.TLS or security == "tls": return 465 if security == TransportSecurity.PLAIN or security == "plain": return 25 return 587 def default_imap_port(security: TransportSecurity | str | None) -> int: if security == TransportSecurity.TLS or security == "tls": return 993 return 143