""" User Schemas Pydantic schemas for user-related requests and responses. Provides input validation and output serialization. """ from pydantic import BaseModel, EmailStr, Field, field_validator from typing import Optional, List from datetime import datetime # ========================================== # Base Schemas # ========================================== class UserBase(BaseModel): """Base user schema with common fields.""" username: str = Field(..., min_length=3, max_length=80, description="Username (3-80 characters)") email: EmailStr = Field(..., description="Email address") name: str = Field(..., min_length=1, max_length=120, description="Full name") preferred_username: Optional[str] = Field(None, max_length=80, description="Preferred display name") # ========================================== # Request Schemas (Input) # ========================================== class UserCreate(UserBase): """Schema for creating a new user.""" password: str = Field(..., min_length=8, description="Password (minimum 8 characters)") password_confirm: str = Field(..., description="Password confirmation") role: Optional[str] = Field("user", description="User role") is_admin: Optional[bool] = Field(False, description="Admin flag") is_active: Optional[bool] = Field(True, description="Active status") permissions: Optional[List[str]] = Field(default_factory=list, description="List of permissions") @field_validator('password') @classmethod def validate_password(cls, v: str) -> str: """Validate password strength.""" if len(v) < 8: raise ValueError('Password must be at least 8 characters long') return v @field_validator('password_confirm') @classmethod def passwords_match(cls, v: str, info) -> str: """Validate that passwords match.""" if 'password' in info.data and v != info.data['password']: raise ValueError('Passwords do not match') return v class UserUpdate(BaseModel): """Schema for updating a user.""" username: Optional[str] = Field(None, min_length=3, max_length=80) email: Optional[EmailStr] = None name: Optional[str] = Field(None, min_length=1, max_length=120) preferred_username: Optional[str] = Field(None, max_length=80) role: Optional[str] = None is_admin: Optional[bool] = None is_active: Optional[bool] = None permissions: Optional[List[str]] = None new_password: Optional[str] = Field(None, min_length=8, description="New password (optional)") @field_validator('new_password') @classmethod def validate_password(cls, v: Optional[str]) -> Optional[str]: """Validate password strength if provided.""" if v is not None and len(v) < 8: raise ValueError('Password must be at least 8 characters long') return v class UserLogin(BaseModel): """Schema for user login.""" username: str = Field(..., description="Username") password: str = Field(..., description="Password") class PasswordChange(BaseModel): """Schema for changing user password.""" current_password: str = Field(..., description="Current password") new_password: str = Field(..., min_length=8, description="New password") new_password_confirm: str = Field(..., description="New password confirmation") @field_validator('new_password') @classmethod def validate_password(cls, v: str) -> str: """Validate password strength.""" if len(v) < 8: raise ValueError('Password must be at least 8 characters long') return v @field_validator('new_password_confirm') @classmethod def passwords_match(cls, v: str, info) -> str: """Validate that passwords match.""" if 'new_password' in info.data and v != info.data['new_password']: raise ValueError('Passwords do not match') return v # ========================================== # Response Schemas (Output) # ========================================== class UserResponse(UserBase): """Schema for user responses.""" id: int role: str is_admin: bool is_active: bool permissions: List[str] created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class UserListResponse(BaseModel): """Schema for paginated user list.""" users: List[UserResponse] total: int page: int per_page: int total_pages: int class UserStatistics(BaseModel): """Schema for user statistics.""" total_users: int active_users: int inactive_users: int admin_users: int