"""Pydantic data models for type safety and validation.""" from datetime import datetime from typing import Literal from pydantic import BaseModel, Field class VendorRow(BaseModel): """Vendor information from VENDOR table.""" vendor_id: int = Field(alias="VENDOR_ID") name: str = Field(alias="NAME") class Config: populate_by_name = True class ArtistRow(BaseModel): """Artist information from GLOBAL_PARTICIPANT table.""" artist_uuid: str = Field(alias="ARTIST_UUID") artist_name: str = Field(alias="ARTIST_NAME") spotify_id: str | None = Field(None, alias="SPOTIFY_ID") class Config: populate_by_name = True class VendorWithBrandRow(BaseModel): """Vendor with brand information for dropdown.""" vendor_id: int = Field(alias="VENDOR_ID") vendor_name: str = Field(alias="VENDOR_NAME") brand_name: str | None = Field( None, alias="BRAND_NAME" ) # Single brand display name def get_display_text(self) -> str: """Format for dropdown: 'Vendor Name (Brand) - ID: 123'""" brand_part = f" ({self.brand_name})" if self.brand_name else "" return f"{self.vendor_name}{brand_part} - ID: {self.vendor_id}" class Config: populate_by_name = True class RosterRow(BaseModel): """Roster entry from unified roster view (aggregated by unique vendor+artist+subaccount).""" vendor_id: int vendor_name: str | None = None brand_name: str | None = None # Single brand display name artist_uuid: str artist_name: str | None = None subaccount_id: int rep_type: str | None = None # Single value: 'MAIN', 'LOCAL', or NULL main_status: str | None = None country_codes: str | None = None # Comma-separated list of countries is_artist_team: bool | None = None created_at: datetime | None = None class AddArtistForm(BaseModel): """Form data for adding an artist to a roster.""" vendor_id: int artist_uuid: str subaccount_id: int roster_type: Literal["MAIN_REP", "LOCAL_REP", "ARTIST_ROSTER"] country_code: str | None = None status: str = "ACTIVE" is_artist_team: bool = False def validate_fields(self) -> list[str]: """ Validate form fields based on roster type. Returns: List of validation error messages (empty if valid) """ errors = [] if self.roster_type == "LOCAL_REP": if not self.country_code: errors.append("Country code is required for LOCAL_REP roster") elif len(self.country_code) != 2: errors.append("Country code must be exactly 2 characters (ISO Alpha-2)") elif not self.country_code.isupper(): errors.append("Country code must be uppercase (e.g., US, GB, JP)") elif not self.country_code.isalpha(): errors.append("Country code must contain only letters") elif self.roster_type in ["MAIN_REP", "ARTIST_ROSTER"]: if self.country_code: errors.append( f"Country code should not be provided for {self.roster_type} roster" ) return errors class RosterFilters(BaseModel): """Filters for roster view search.""" vendor_id: int | None = None vendor_name: str | None = None artist_uuid: str | None = None artist_name: str | None = None subaccount_id: int | None = None limit: int = 100 offset: int = 0