from apollo_utils.core.constants.dsp import DSP_SPOTIFY_APPLE from apollo_utils.service.schemas.fields.enum import EnumField from apollo_utils.service.schemas.fields.reach import Reach from marshmallow import ValidationError, fields, post_load, validate from apollo_playlists_messages.config import FULL_APP_NAME from apollo_playlists_messages.constants import EventCode from apollo_playlists_messages.schemas.base import SchemaWithMeta CURRENT_POSITION_EVENT_CODES = (EventCode.ENTRY, EventCode.MAJOR_MOVE) PREVIOUS_POSITION_EVENT_CODES = (EventCode.EXIT, EventCode.MAJOR_MOVE) class MetaItem(SchemaWithMeta): dsp = EnumField(DSP_SPOTIFY_APPLE, load_as="value", required=True) country_code = fields.List(fields.Str(required=True), missing=[]) top_country_code = fields.List(fields.Str(required=True), missing=[]) class Playlist(SchemaWithMeta): id = fields.Str(required=True) name = fields.Str(required=True) updated_at = fields.Str(required=True, data_key="last_updated_at") class TrackCurrentPositionMixin: current_position = fields.Int(required=True) class TrackPreviousPositionMixin: previous_position = fields.Int(required=True) class TrackMixin: class Track(SchemaWithMeta): class Artist(SchemaWithMeta): id = fields.Str(required=True) name = fields.Str(required=True) order = fields.Int(required=True) id = fields.Str(required=True) isrc = fields.Str(required=True) name = fields.Str(required=True) image_url = fields.Str(required=True) artists = fields.List(fields.Nested(Artist, required=True), validate=validate.Length(min=1)) track = fields.Nested(Track, required=True) def content_mixin(with_track: bool = False, event_code: EventCode = None): base_cls = [] if with_track: base_cls.append(TrackMixin) if event_code in CURRENT_POSITION_EVENT_CODES: base_cls.append(TrackCurrentPositionMixin) if event_code in PREVIOUS_POSITION_EVENT_CODES: base_cls.append(TrackPreviousPositionMixin) class Content(*base_cls, SchemaWithMeta): playlist = fields.Nested(Playlist, required=True) class ContentMixin: content = Reach(fields.Nested(Content, required=True), data_key="data", path="content") return ContentMixin class FullEvent(SchemaWithMeta): id = fields.Int(required=True) meta = fields.Nested(MetaItem, required=True) @post_load def prepare_loaded(self, data, **kwargs): data = self._flatten(data) data = self._parse_spotify_country_code(data) return data def _parse_spotify_country_code(self, data): dsp, country_codes, playlist = data["dsp"], data["country_code"], data["playlist"] if dsp == DSP_SPOTIFY_APPLE.APPLE.value and len(country_codes) == 0: raise ValidationError( f"{FULL_APP_NAME}.full_event_schema expect apple playlist={playlist['id']}\n" f"has at least one country_code, but received country_code={country_codes}\n" f"for event={data['id']}" ) if dsp == DSP_SPOTIFY_APPLE.SPOTIFY.value: if len(country_codes) > 1: raise ValidationError( f"{FULL_APP_NAME}.full_event_schema expect spotify playlist={playlist['id']}\n" f"has one and only one country_code, but received country_code={country_codes}\n" f"for event={data['id']}" ) elif len(country_codes) == 0: # handle empty cc case playlist["country_code"] = "" data["country_code"] = [""] else: playlist["country_code"] = country_codes[0] return data def _flatten(self, data): meta = data.pop("meta") content = data.pop("content") for extra in (meta, content): data.update(extra) return data class PlaylistEntryFullEvent(content_mixin(with_track=True, event_code=EventCode.ENTRY), FullEvent): pass class PlaylistUpdateFullEvent(content_mixin(with_track=False), FullEvent): pass class PlaylistExitFullEvent(content_mixin(with_track=True, event_code=EventCode.EXIT), FullEvent): pass class PlaylistMajorMoveFullEvent(content_mixin(with_track=True, event_code=EventCode.MAJOR_MOVE), FullEvent): pass EVENT_CODE_TO_FULL_SCHEMA = { EventCode.ENTRY: PlaylistEntryFullEvent, EventCode.UPDATE: PlaylistUpdateFullEvent, EventCode.EXIT: PlaylistExitFullEvent, EventCode.MAJOR_MOVE: PlaylistMajorMoveFullEvent }