from apollo_utils.service.schemas.base import RequestSchema from apollo_utils.service.schemas.fields.isrc import IsrcField from apollo_utils.service.schemas.fields.list import SplitUniqueListField from apollo_utils.service.schemas.fields.market import DelphiMarketField from apollo_utils.service.schemas.mixins.date import DateRangeMixin from apollo_utils.service.schemas.mixins.isrc import get_isrc_list_mixin from marshmallow import Schema, ValidationError, fields, post_dump, post_load, validate, validates_schema from typing import Optional from server.constants.delphi.streams.group_by import StreamsGroupBy from server.constants.delphi.streams.include import StreamsInclude from server.legacy.core.constants import DELPHI_GLOBAL_MARKET, GLOBAL_MARKET from server.legacy.core.deserializers import BaseLimitOffsetDeserializer, OneOrListField from server.legacy.delphi.constants import VideosInclude, VideosOnly from server.schemas.delphi.deserializers.videos.analytics import DelphiVideoAnalyticsParams from server.schemas.delphi.deserializers.videos.misc import DelphiVideosParams from server.schemas.delphi.mixins.skippers import SkipEmptyDataMixin class BaseAmazonStreamsDeserializer(BaseLimitOffsetDeserializer, DateRangeMixin): """Base class for amazon streams delphi endpoints.""" country_code = fields.Str(validate=validate.Length(min=2, max=9)) group_by = OneOrListField( fields.Str(validate=validate.OneOf(StreamsGroupBy.AMAZON_FIELDS)), missing=None, unify=True, validate=validate.Length(min=1), ) class AmazonTracksStreamsDeserializer(BaseAmazonStreamsDeserializer, get_isrc_list_mixin()): """Query arguments validation schema for amazon streams for several tracks.""" pass class AmazonTracksStreamsV1Deserializer(RequestSchema, AmazonTracksStreamsDeserializer): """Query arguments validation schema for amazon streams for several tracks with combine_isrc option.""" combine_isrc = fields.Boolean(default=False, missing=False) combine_tiers = fields.Boolean(default=False, missing=False) class MarketField(fields.String): def _serialize(self, value, attr, obj, **kwargs) -> Optional[str]: value = super()._serialize(value, attr, obj, **kwargs) return DELPHI_GLOBAL_MARKET if value == GLOBAL_MARKET else value class TrackStreamDemographicsItem(Schema): """Isrc item schema for for Spotify Track Stream Demographics request data.""" isrc = IsrcField(required=True) start_date = fields.Date(required=True, data_key="start") end_date = fields.Date(required=True, data_key="end") @validates_schema def validate_dates(self, data, *args, **kwargs): if "start_date" in data and "end_date" in data and data["start_date"] > data["end_date"]: raise ValidationError("End must be greater than start.") class TrackStreamDemographicsRequest(RequestSchema): """Schema for for Track Stream Demographics request data. Allows to request several isrc with different date ranges. """ market = OneOrListField( DelphiMarketField(validate=validate.Length(min=2)), missing=None, unify=True, validate=validate.Length(min=1) ) items = fields.Nested(TrackStreamDemographicsItem, required=True, many=True) include = SplitUniqueListField( fields.String(validate=validate.OneOf(StreamsInclude.ALL_FIELDS)), missing=[StreamsInclude.DEMOGRAPHICS] ) combine_isrc = fields.Bool(default=False, missing=False) class DemographicsGendersSchema(SkipEmptyDataMixin, Schema): """Response Schema for for Track Demographics gender data.""" female = fields.Integer(default=0, missing=0) male = fields.Integer(default=0, missing=0) unknown = fields.Integer(default=0, missing=0) neutral = fields.Integer(default=0, missing=0) class DemographicsSpotifyAgeBands(SkipEmptyDataMixin, Schema): """Response Schema for for Spotify Demographics age bands data.""" female_0_17 = fields.Integer(default=0, missing=0) male_0_17 = fields.Integer(default=0, missing=0) all_0_17 = fields.Integer(default=0, missing=0) neutral_0_17 = fields.Integer(default=0, missing=0) unknown_0_17 = fields.Integer(default=0, missing=0) female_18_22 = fields.Integer(default=0, missing=0) male_18_22 = fields.Integer(default=0, missing=0) all_18_22 = fields.Integer(default=0, missing=0) neutral_18_22 = fields.Integer(default=0, missing=0) unknown_18_22 = fields.Integer(default=0, missing=0) female_23_27 = fields.Integer(default=0, missing=0) male_23_27 = fields.Integer(default=0, missing=0) all_23_27 = fields.Integer(default=0, missing=0) neutral_23_27 = fields.Integer(default=0, missing=0) unknown_23_27 = fields.Integer(default=0, missing=0) female_28_34 = fields.Integer(default=0, missing=0) male_28_34 = fields.Integer(default=0, missing=0) all_28_34 = fields.Integer(default=0, missing=0) neutral_28_34 = fields.Integer(default=0, missing=0) unknown_28_34 = fields.Integer(default=0, missing=0) female_35_44 = fields.Integer(default=0, missing=0) male_35_44 = fields.Integer(default=0, missing=0) all_35_44 = fields.Integer(default=0, missing=0) neutral_35_44 = fields.Integer(default=0, missing=0) unknown_35_44 = fields.Integer(default=0, missing=0) female_45_59 = fields.Integer(default=0, missing=0) male_45_59 = fields.Integer(default=0, missing=0) all_45_59 = fields.Integer(default=0, missing=0) neutral_45_59 = fields.Integer(default=0, missing=0) unknown_45_59 = fields.Integer(default=0, missing=0) female_60_150 = fields.Integer(default=0, missing=0) male_60_150 = fields.Integer(default=0, missing=0) all_60_150 = fields.Integer(default=0, missing=0) neutral_60_150 = fields.Integer(default=0, missing=0) unknown_60_150 = fields.Integer(default=0, missing=0) female_unknown = fields.Integer(default=0, missing=0) male_unknown = fields.Integer(default=0, missing=0) all_unknown = fields.Integer(default=0, missing=0) neutral_unknown = fields.Integer(default=0, missing=0) unknown_unknown = fields.Integer(default=0, missing=0) class DemographicsAppleAgeBands(SkipEmptyDataMixin, Schema): """Response Schema for for Apple Demographics age bands data.""" female_0_17 = fields.Integer(default=0, missing=0) male_0_17 = fields.Integer(default=0, missing=0) all_0_17 = fields.Integer(default=0, missing=0) unknown_0_17 = fields.Integer(default=0, missing=0) female_18_24 = fields.Integer(default=0, missing=0) male_18_24 = fields.Integer(default=0, missing=0) all_18_24 = fields.Integer(default=0, missing=0) unknown_18_24 = fields.Integer(default=0, missing=0) female_25_34 = fields.Integer(default=0, missing=0) male_25_34 = fields.Integer(default=0, missing=0) all_25_34 = fields.Integer(default=0, missing=0) unknown_25_34 = fields.Integer(default=0, missing=0) female_35_44 = fields.Integer(default=0, missing=0) male_35_44 = fields.Integer(default=0, missing=0) all_35_44 = fields.Integer(default=0, missing=0) unknown_35_44 = fields.Integer(default=0, missing=0) female_45_54 = fields.Integer(default=0, missing=0) male_45_54 = fields.Integer(default=0, missing=0) all_45_54 = fields.Integer(default=0, missing=0) unknown_45_54 = fields.Integer(default=0, missing=0) female_55_64 = fields.Integer(default=0, missing=0) male_55_64 = fields.Integer(default=0, missing=0) all_55_64 = fields.Integer(default=0, missing=0) unknown_55_64 = fields.Integer(default=0, missing=0) female_65_plus = fields.Integer(default=0, missing=0) male_65_plus = fields.Integer(default=0, missing=0) all_65_plus = fields.Integer(default=0, missing=0) unknown_65_plus = fields.Integer(default=0, missing=0) female_unknown = fields.Integer(default=0, missing=0) male_unknown = fields.Integer(default=0, missing=0) all_unknown = fields.Integer(default=0, missing=0) unknown_unknown = fields.Integer(default=0, missing=0) class TrackStreamDemographicItem(Schema): """Base response Schema for track demographics data.""" isrc = fields.String() dsp = fields.String() country_code = fields.String(data_key="market") genders = fields.Nested(DemographicsGendersSchema) streams = fields.Integer() @post_dump def skip_missing(self, data, *args, **kwargs): return {k: v for k, v in data.items() if v is not None} class TrackStreamSpotifyDemographicItem(TrackStreamDemographicItem): """Response Schema for spotify track demographics data.""" spotify_age_bands = fields.Nested(DemographicsSpotifyAgeBands, data_key="age_bands") skips = fields.Function(lambda obj: (obj.get("spotify_streams_info") or {}).get("skips")) saves = fields.Function(lambda obj: (obj.get("spotify_streams_info") or {}).get("saves")) class TrackStreamAppleDemographicItem(TrackStreamDemographicItem): """Response Schema for apple track demographics data.""" apple_age_bands = fields.Nested(DemographicsAppleAgeBands, data_key="age_bands") skips = fields.Function(lambda obj: (obj.get("apple_streams_info") or {}).get("skips")) saves = fields.Function(lambda obj: (obj.get("apple_streams_info") or {}).get("saves")) class FirstStreamsDateResponse(Schema): """Response schema for first streams date.""" apple = fields.Date() spotify = fields.Date() amazon = fields.Date() class TotalStreamsCountResponse(Schema): """Response schema for total streams count.""" _global = fields.Integer() market = fields.Integer() first_stream_date = fields.Nested(FirstStreamsDateResponse) class DelphiVideoAnalyticsOnlyMixin: """Include like filter for result fields.""" only = SplitUniqueListField( fields.String(required=True, validate=validate.OneOf(VideosOnly.ALL_FIELDS)), required=False ) class DelphiVideoAnalyticsInputParams(DelphiVideoAnalyticsParams): include = SplitUniqueListField( fields.String( required=True, validate=validate.OneOf((VideosInclude.ALL, VideosInclude.DEMOGRAPHICS, VideosInclude.VIEWS)) ) ) @post_load def parse_include(self, data, **kwargs): include = data.get("include") if include: if not data.get("only") and VideosInclude.ALL not in include and len(set(include)) == 1: data["only"] = VideosInclude.ONLY_MAPPING[include[0]] del data["include"] return data class DelphiVideosInputParams(DelphiVideosParams): views = fields.Integer(required=False, missing=None)