from marshmallow import Schema, fields, EXCLUDE, post_load, pre_load from charts_feed_messages.constants import FeedMessageFields from charts_feed_messages.utils.feed_message import FeedMessageFieldsFabricator class MetaExcludeUnknown(Schema): """Schema to exclude unknown fields to have a predicted structure of event/message""" class Meta: unknown = EXCLUDE class MetaNested(MetaExcludeUnknown): """Schema for nested meta of an event/message ex: { "domain": "message", "dsp": "spotify"/ "apple", "country_code": "us" or any other country code, "views": ["feed_message", "push_message"], "type": one of: "starred_track_top_chart_entry"/"starred_track_top_chart_exit"/"starred_track_top_chart_move" "subject": "starred_track_in_chart" } """ domain = fields.String() dsp = fields.String(required=True) country_code = fields.String(required=True) views = fields.List(fields.String()) type = fields.String(required=True) subject = fields.String(required=True) class IncomingEvent(MetaExcludeUnknown): """Schema for incoming event ex: "id": str # message db_record id "app": str = "apollo" "code": str = "messages_created" "ttl": int # period (sec) during the event is actual "created_at": datetime "meta": { "domain": "message", "dsp": str = "spotify", "country_code": str = "us", "views": ["feed_message", push_message"], "type": str = one of: "starred_track_top_chart_entry" "starred_track_top_chart_exit" "starred_track_top_chart_move", } "publisher": { "id": str = "user_data_api_ecs_web_test", "owner": str = "apollo", "type": str = "ecs_test" "instance": str = "ecs:test86876" "env": str = "test" } """ id = fields.Integer(required=True) app = fields.String(required=True) code = fields.String(required=True) ttl = fields.Integer(required=True) created_at = fields.DateTime(required=True) meta = fields.Nested(MetaNested, required=True) class NestedMessageEventDataResipient(MetaExcludeUnknown): """Schema for nested resipient data ex: { "devices": [ { "expo_token": "expo_token_string", "id": "123" }, ] } """ class DeviceNested(MetaExcludeUnknown): expo_token = fields.String() id = fields.Integer() devices = fields.List(fields.Nested(DeviceNested)) class NestedMessageEventDataContent(MetaExcludeUnknown): """Nested content data ex: { "track": { "id": "some_track_id", "isrc": "some_track_isrc", "name": "some_track_name", "image_url": "some_track_image_url", "artists": [{"id": "some_artists_id", "name": "some_artist_name"}], "favorites_id": "123", }, "chart": { "dsp": "spotify"/ "apple", "country_code": "us" or any other country code, "type": "viral", "breakdown": "daily" }, "current_position": can be integer or None (based on meta.type), "previous_position": can be integer or None (based on meta.type), } """ class NestedContentTrack(MetaExcludeUnknown): class NestedTrackArtists(MetaExcludeUnknown): id = fields.String(required=True) name = fields.String(required=True) id = fields.String(required=True) isrc = fields.String(required=True) name = fields.String(required=True) image_url = fields.String(required=True) artists = fields.List(fields.Nested(NestedTrackArtists)) favorites_id = fields.Integer() class NestedContentChart(MetaExcludeUnknown): dsp = fields.String(required=True) country_code = fields.String(required=True) type = fields.String(allow_none=True) breakdown = fields.String(required=True) track = fields.Nested(NestedContentTrack()) chart = fields.Nested(NestedContentChart()) current_position = fields.Integer(required=True, allow_none=True) previous_position = fields.Integer(required=True, allow_none=True) class NestedData(MetaExcludeUnknown): """Schema for message data field""" content = fields.Nested(NestedMessageEventDataContent()) recipient = fields.Nested(NestedMessageEventDataResipient) class MessageEventWithData(MetaExcludeUnknown): """Schema for full message with data got from User Data api service/messages/list/ """ id = fields.Integer(required=True) ttl = fields.Integer(required=True) created_at = fields.DateTime(required=True) updated_at = fields.DateTime(required=True) event_id = fields.Integer(required=True) account_id = fields.Integer() message_id = fields.Integer(allow_none=True) meta = fields.Nested(MetaNested(), required=True) data = fields.Nested(NestedData(), required=True) class FeedMessageSchema(MessageEventWithData): """Schema to create a Feed Massage from basic MessageEventWithData""" class Meta: exclude = FeedMessageFields.EXCLUDE.value include = FeedMessageFields.INCLUDE.value unknown = EXCLUDE @pre_load def update_ttl(self, data, *args, **kwargs): """Get and update ttl field Args: data: Basic Message (MessageEventWithData) that is loading to the schema """ data["ttl"] = data["ttl"] - (self.context["utcnow"] - data["created_at"]).total_seconds() return data @post_load def update_data(self, data, *args, **kwargs): """Extend fields with all needed information Args: data: loaded data *args: **kwargs: Returns: """ fields_fabricator = FeedMessageFieldsFabricator(message=data) data["data"]["content"]["chart"] = fields_fabricator.get_chart() data["data"]["content"]["body"] = fields_fabricator.get_body() # Need to add this field here because Marshmallow include works unpredictably with many fields to include data["data"]["content"]["title"] = "" return data