import re from pydantic import BaseModel, ConfigDict, Field, model_validator, field_validator from datetime import datetime, date from typing import Optional _EMAIL_RE = re.compile(r'^[^@\s]+@[^@\s]+\.[^@\s]+$') class PersonCreate(BaseModel): model_config = ConfigDict(extra="forbid") name: Optional[str] = Field(None, max_length=255) # legacy fallback — auto-split into first/last if provided alone first_name: Optional[str] = Field(None, max_length=100) last_name: Optional[str] = Field(None, max_length=100) nickname: Optional[str] = Field(None, max_length=100) email: Optional[str] = Field(None, max_length=255) phone: Optional[str] = Field(None, max_length=50) mobile: Optional[str] = Field(None, max_length=50) address: Optional[str] = Field(None, max_length=2000) birthday: Optional[date] = None category: Optional[str] = Field(None, max_length=100) is_favourite: bool = False company: Optional[str] = Field(None, max_length=255) job_title: Optional[str] = Field(None, max_length=255) notes: Optional[str] = Field(None, max_length=5000) @model_validator(mode='after') def require_some_name(self) -> 'PersonCreate': if not any([ self.name and self.name.strip(), self.first_name and self.first_name.strip(), self.last_name and self.last_name.strip(), self.nickname and self.nickname.strip(), ]): 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): model_config = ConfigDict(extra="forbid") # name is intentionally omitted — always computed from first/last/nickname first_name: Optional[str] = Field(None, max_length=100) last_name: Optional[str] = Field(None, max_length=100) nickname: Optional[str] = Field(None, max_length=100) email: Optional[str] = Field(None, max_length=255) phone: Optional[str] = Field(None, max_length=50) mobile: Optional[str] = Field(None, max_length=50) address: Optional[str] = Field(None, max_length=2000) birthday: Optional[date] = None category: Optional[str] = Field(None, max_length=100) is_favourite: Optional[bool] = None company: Optional[str] = Field(None, max_length=255) job_title: Optional[str] = Field(None, max_length=255) notes: Optional[str] = Field(None, max_length=5000) @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] is_favourite: bool company: Optional[str] job_title: Optional[str] notes: Optional[str] linked_user_id: Optional[int] = None is_umbral_contact: bool = False shared_fields: Optional[dict] = None created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True)