"""Contributor schemas.""" from uuid import UUID from pydantic import UUID4, BaseModel, ConfigDict, Field, field_validator from contributor.api.schemas.schema_examples import with_example class ContributorKey(BaseModel): model_config = ConfigDict(title="Key identifying a contributor.") uuid: UUID4 = Field(description="Unique identifier for this contributor.") class LabelSoundRecordingKey(BaseModel): model_config = ConfigDict(title="Key identifying a label sound recording.") id: UUID4 = Field(description="The label sound recording's internal ID.") class GlobalParticipantKey(BaseModel): model_config = ConfigDict(title="Key identifying a global participant.") id: UUID = Field(description="The global participant's unique identifier.") class ContributorDataloaderRequest(BaseModel): model_config = ConfigDict( title="Request body for loading multiple contributors by key.", json_schema_extra=with_example("contributor_dataloader_request"), ) contributors: list[ContributorKey] = Field( description="List of contributor keys to load." ) class ContributorIdentifierNormalizationMixin: @field_validator("spotify_id", "apple_music_id", mode="before", check_fields=False) @classmethod def coalesce_blank_to_none(cls, value: str | None) -> str | None: if value is None: return None trimmed_value = value.strip() if trimmed_value == "": return None return trimmed_value @field_validator("spotify_id", check_fields=False) @classmethod def format_spotify_id(cls, spotify_id: str | None) -> str | None: if spotify_id is None: return spotify_id return spotify_id.replace("spotify:artist:", "") class ContributorInput(ContributorIdentifierNormalizationMixin, BaseModel): model_config = ConfigDict( title="Input values for creating or updating a contributor", json_schema_extra=with_example("contributor_input"), ) name: str = Field(description="The contributor's display name.") spotify_id: str | None = Field(default=None, description="Spotify artist ID.") apple_music_id: str | None = Field( default=None, description="Apple Music artist ID." ) spotify_artist_key: str | None = Field( default=None, description="Spotify artist key used for 'Artist Profile Protection' APP authorization.", ) isni: str | None = Field( default=None, description="International Standard Name Identifier (ISNI)." ) global_participant_uuid: str | None = Field( default=None, description="UUID of the associated global participant." ) class CreateContributorRequest(BaseModel): model_config = ConfigDict( title="Request body for creating a contributor", json_schema_extra=with_example("create_contributor_request"), ) input: ContributorInput = Field(description="Contributor metadata.") vendor_id: int = Field(description="Integer vendor ID from art_relations.") subaccount_id: int | None = Field( default=None, description="Subaccount ID, if the label is a subaccount." ) class UpdateContributorRequest(ContributorIdentifierNormalizationMixin, BaseModel): model_config = ConfigDict( title="Request body for updating a contributor", json_schema_extra=with_example("update_contributor_request"), ) spotify_id: str | None = Field( default=None, description="Spotify artist ID. If omitted, the existing value is preserved.", ) apple_music_id: str | None = Field( default=None, description="Apple Music artist ID." ) spotify_artist_key: str | None = Field( default=None, description="Spotify artist key used for 'Artist Profile Protection' APP authorization.", ) isni: str | None = Field( default=None, description="International Standard Name Identifier (ISNI)." ) global_participant_uuid: str | None = Field( default=None, description="UUID of the associated global participant." ) class RenameContributorRequest(BaseModel): model_config = ConfigDict(title="Request body for renaming a contributor") name: str = Field(description="New display name for the contributor.") class MergeContributorsRequest(BaseModel): model_config = ConfigDict( title="Request body for merging contributors", json_schema_extra=with_example("merge_contributors_request"), ) duplicate_contributor: ContributorKey = Field( description="The contributor to be merged and deleted." ) class Label(BaseModel): model_config = ConfigDict( title="A label associated with a contributor.", json_schema_extra=with_example("label"), ) uuid: UUID | None = Field(default=None, description="UUID of the label.") vendor_id: int | None = Field( default=None, description="Integer vendor ID from art_relations." ) subaccount_id: int | None = Field( default=None, description="Subaccount ID, if the label is a subaccount." ) class Contributor(ContributorKey): model_config = ConfigDict( title="A contributor associated with a label.", json_schema_extra=with_example("contributor"), ) id: int = Field(description="Internal node ID.") uuid: UUID4 = Field(description="Unique identifier for this contributor.") name: str = Field(description="Display name.") spotify_id: str | None = Field(default=None, description="Spotify artist ID.") apple_music_id: str | None = Field( default=None, description="Apple Music artist ID." ) spotify_artist_key: str | None = Field( default=None, description="Spotify artist key used for 'Artist Profile Protection' APP authorization.", ) isni: str | None = Field( default=None, description="International Standard Name Identifier (ISNI)." ) label: Label = Field(description="Label associated with the contributor.") company_brand_uuid: UUID4 | None = Field( default=None, description="UUID of the CompanyBrand node." ) parent_company_uuid: UUID4 | None = Field( default=None, description="UUID of the ParentCompany node." ) artist_info_ids: list[int] | None = Field( default=[], description="List of associated ArtistInfo id records." ) global_participant: GlobalParticipantKey | None = Field( default=None, description="Associated global participant, if any." ) class ParticipationRole(BaseModel): model_config = ConfigDict(title="A participation role.") apple_role_name: str | None = Field( default=None, description="The Apple role name." ) category: dict[str, str] | None = Field( default=None, description="The participation role category with name and uuid." ) ddex_role_name: str | None = Field(default=None, description="The DDEX role name.") name: str = Field(description="The participation role name.") class SoundRecordingParticipation(BaseModel): model_config = ConfigDict( title="A sound recording participation for a contributor." ) contributor: ContributorKey = Field( description="The contributor associated with this participation." ) label_sound_recording: LabelSoundRecordingKey = Field( description="The label sound recording's internal ID." ) role: ParticipationRole = Field(description="The participation role.") sequence_number: int = Field( description="The sequence number of the participation." ) class SoundRecordingParticipationsResponse(BaseModel): model_config = ConfigDict(title="Sound recording participations for a contributor.") contributor: ContributorKey = Field(description="UUID of the contributor.") participations: list[SoundRecordingParticipation] = Field( default=[], description="List of sound recording participations." ) # Backward-compatible re-exports as modules are split by resource boundary.