import re from pydantic import BaseModel, ConfigDict, model_validator, field_validator from datetime import datetime, date from typing import Optional _EMAIL_RE = re.compile(r'^[^@\s]+@[^@\s]+\.[^@\s]+$') class PersonCreate(BaseModel): name: Optional[str] = None # legacy fallback — auto-split into first/last if provided alone first_name: Optional[str] = None last_name: Optional[str] = None nickname: Optional[str] = None email: Optional[str] = None phone: Optional[str] = None mobile: Optional[str] = None address: Optional[str] = None birthday: Optional[date] = None category: Optional[str] = None is_favourite: bool = False company: Optional[str] = None job_title: Optional[str] = None notes: Optional[str] = None @model_validator(mode='after') def require_some_name(self) -> 'PersonCreate': if not any([self.name, self.first_name, self.last_name, self.nickname]): raise ValueError('At least one name field is required') return self @field_validator('email') @classmethod def validate_email(cls, v: str | None) -> str | None: if v and not _EMAIL_RE.match(v): raise ValueError('Invalid email address') return v class PersonUpdate(BaseModel): # name is intentionally omitted — always computed from first/last/nickname first_name: Optional[str] = None last_name: Optional[str] = None nickname: Optional[str] = None email: Optional[str] = None phone: Optional[str] = None mobile: Optional[str] = None address: Optional[str] = None birthday: Optional[date] = None category: Optional[str] = None is_favourite: Optional[bool] = None company: Optional[str] = None job_title: Optional[str] = None notes: Optional[str] = None @field_validator('email') @classmethod def validate_email(cls, v: str | None) -> str | None: if v and not _EMAIL_RE.match(v): raise ValueError('Invalid email address') return v class PersonResponse(BaseModel): id: int name: str first_name: Optional[str] last_name: Optional[str] nickname: Optional[str] email: Optional[str] phone: Optional[str] mobile: Optional[str] address: Optional[str] birthday: Optional[date] category: Optional[str] relationship: Optional[str] # deprecated — kept for legacy calendar/birthday compat, will be removed is_favourite: bool company: Optional[str] job_title: Optional[str] notes: Optional[str] created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True)