diff --git a/nrk_psapi/models/__init__.py b/nrk_psapi/models/__init__.py index 164d1e6..1dd4aee 100644 --- a/nrk_psapi/models/__init__.py +++ b/nrk_psapi/models/__init__.py @@ -1,4 +1,5 @@ """nrk-psapi models.""" + from __future__ import annotations from functools import cache @@ -158,12 +159,13 @@ @cache -def get_operation(path: str) -> Operation | None: # pragma: no cover +def get_operation(path: str) -> Operation | None: # pragma: no cover for operation in OPERATIONS.values(): if operation["path"] == path: return operation return None + __all__ = [ "Availability", "AvailabilityDetailed", diff --git a/nrk_psapi/models/catalog.py b/nrk_psapi/models/catalog.py index 630dd75..dceae9e 100644 --- a/nrk_psapi/models/catalog.py +++ b/nrk_psapi/models/catalog.py @@ -88,7 +88,9 @@ class Category(BaseDataClassORJSONMixin): id: str name: str | None = None - display_value: str | None = field(default=None, metadata=field_options(alias="displayValue")) + display_value: str | None = field( + default=None, metadata=field_options(alias="displayValue") + ) def __str__(self): return self.display_value or self.id @@ -135,22 +137,34 @@ class Episode(BaseDataClassORJSONMixin): type: EpisodeType episode_id: str = field(metadata=field_options(alias="episodeId")) titles: Titles - duration: timedelta = field(metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat)) + duration: timedelta = field( + metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat) + ) date: datetime usage_rights: UsageRights = field(metadata=field_options(alias="usageRights")) availability: Availability program_information: ProgramInformation | None = field( - default=None, - metadata=field_options(alias="programInformation")) + default=None, metadata=field_options(alias="programInformation") + ) image: list[Image] | None = None - square_image: list[Image] | None = field(default=None, metadata=field_options(alias="squareImage")) + square_image: list[Image] | None = field( + default=None, metadata=field_options(alias="squareImage") + ) category: Category | None = None badges: list | None = None - duration_in_seconds: int | None = field(default=None, metadata=field_options(alias="durationInSeconds")) + duration_in_seconds: int | None = field( + default=None, metadata=field_options(alias="durationInSeconds") + ) clip_id: str | None = field(default=None, metadata=field_options(alias="clipId")) - original_title: str | None = field(default=None, metadata=field_options(alias="originalTitle")) - production_year: int | None = field(default=None, metadata=field_options(alias="productionYear")) - index_points: list[IndexPoint] | None = field(default=None, metadata=field_options(alias="indexPoints")) + original_title: str | None = field( + default=None, metadata=field_options(alias="originalTitle") + ) + production_year: int | None = field( + default=None, metadata=field_options(alias="productionYear") + ) + index_points: list[IndexPoint] | None = field( + default=None, metadata=field_options(alias="indexPoints") + ) contributors: list[Contributor] | None = None @classmethod @@ -182,7 +196,9 @@ class SeasonBase(BaseDataClassORJSONMixin): _links: Links titles: Titles - has_available_episodes: bool = field(metadata=field_options(alias="hasAvailableEpisodes")) + has_available_episodes: bool = field( + metadata=field_options(alias="hasAvailableEpisodes") + ) episode_count: int = field(metadata=field_options(alias="episodeCount")) image: list[Image] @@ -195,8 +211,11 @@ class SeasonEmbedded(SeasonBase): episodes: list[Episode] | None = field( default=None, metadata=field_options( - deserialize=lambda x: [Episode.from_dict(d) for d in x["_embedded"]["episodes"]], - )) + deserialize=lambda x: [ + Episode.from_dict(d) for d in x["_embedded"]["episodes"] + ], + ), + ) @dataclass @@ -208,13 +227,35 @@ class Season(SeasonBase): episodes: list[Episode] = field( metadata=field_options( alias="_embedded", - deserialize=lambda x: [Episode.from_dict(d) for d in x["episodes"]["_embedded"]["episodes"]], - )) + deserialize=lambda x: [ + Episode.from_dict(d) for d in x["episodes"]["_embedded"]["episodes"] + ], + ) + ) name: str | None = None category: Category | None = None id: str | None = None - square_image: list[Image] | None = field(default=None, metadata=field_options(alias="squareImage")) - backdrop_image: list[Image] | None = field(default=None, metadata=field_options(alias="backdropImage")) + series_id: str | None = None + podcast_id: str | None = None + square_image: list[Image] | None = field( + default=None, metadata=field_options(alias="squareImage") + ) + backdrop_image: list[Image] | None = field( + default=None, metadata=field_options(alias="backdropImage") + ) + + @classmethod + def __pre_deserialize__(cls: type[T], d: T) -> T: + self_id = d.get("_links", {}).get("self", {}).get("href", "").split("/").pop() + series_id = d.get("_links", {}).get("series", {}).get("name") + podcast_id = d.get("_links", {}).get("podcast", {}).get("name") + if series_id: + d["series_id"] = series_id + if podcast_id: + d["podcast_id"] = podcast_id + if not d.get("name") and self_id: + d["name"] = self_id + return d @dataclass @@ -226,8 +267,11 @@ class EpisodesResponse(BaseDataClassORJSONMixin): metadata=field_options( alias="_embedded", deserialize=lambda x: x["episodes"], - )) - series_type: SeriesType | None = field(default=None, metadata=field_options(alias="seriesType")) + ) + ) + series_type: SeriesType | None = field( + default=None, metadata=field_options(alias="seriesType") + ) @dataclass @@ -236,11 +280,21 @@ class PodcastSeries(BaseDataClassORJSONMixin): titles: Titles category: Category image: list[Image] - square_image: list[Image] = field(metadata=field_options(alias="squareImage")) - backdrop_image: list[Image] | None = field(default=None, metadata=field_options(alias="backdropImage")) - poster_image: list[Image] | None = field(default=None, metadata=field_options(alias="posterImage")) - highlighted_episode: str | None = field(default=None, metadata=field_options(alias="highlightedEpisode")) - next_episode: Date | None = field(default=None, metadata=field_options(alias="nextEpisode")) + square_image: list[Image] | None = field( + default=None, metadata=field_options(alias="squareImage") + ) + backdrop_image: list[Image] | None = field( + default=None, metadata=field_options(alias="backdropImage") + ) + poster_image: list[Image] | None = field( + default=None, metadata=field_options(alias="posterImage") + ) + highlighted_episode: str | None = field( + default=None, metadata=field_options(alias="highlightedEpisode") + ) + next_episode: Date | None = field( + default=None, metadata=field_options(alias="nextEpisode") + ) @dataclass @@ -250,7 +304,9 @@ class Podcast(BaseDataClassORJSONMixin): _links: Links type: PodcastType = field(metadata=field_options(alias="type")) series_type: SeriesType = field(metadata=field_options(alias="seriesType")) - season_display_type: SeasonDisplayType = field(metadata=field_options(alias="seasonDisplayType")) + season_display_type: SeasonDisplayType = field( + metadata=field_options(alias="seasonDisplayType") + ) series: PodcastSeries class Config(BaseConfig): @@ -267,8 +323,18 @@ class PodcastStandard(Podcast): default_factory=list, metadata=field_options( alias="_embedded", - deserialize=lambda x: [Episode.from_dict(d) for d in x["episodes"]["_embedded"]["episodes"]], - )) + deserialize=lambda x: [ + Episode.from_dict(d) for d in x["episodes"]["_embedded"]["episodes"] + ], + ), + ) + seasons: list[SeasonLink] = field(default_factory=list) + + @classmethod + def __pre_deserialize__(cls: type[T], d: T) -> T: + season_links = d.get("_links", {}).get("seasons", []) + d["seasons"] = [{"id": d["name"], "title": d["title"]} for d in season_links] + return d @dataclass @@ -279,7 +345,8 @@ class PodcastUmbrella(Podcast): metadata=field_options( alias="_embedded", deserialize=lambda x: [SeasonEmbedded.from_dict(d) for d in x["seasons"]], - )) + ), + ) @dataclass @@ -290,7 +357,14 @@ class PodcastSequential(Podcast): metadata=field_options( alias="_embedded", deserialize=lambda x: [SeasonEmbedded.from_dict(d) for d in x["seasons"]], - )) + ), + ) + + +@dataclass +class SeasonLink(BaseDataClassORJSONMixin): + id: str + title: str @dataclass @@ -313,23 +387,35 @@ class Links(BaseDataClassORJSONMixin): self: Link | None = None manifests: list[Link] | None = None next: Link | None = None - next_links: list[Link] | None = field(default=None, metadata=field_options(alias="nextLinks")) + next_links: list[Link] | None = field( + default=None, metadata=field_options(alias="nextLinks") + ) playback: Link | None = None series: Link | None = None season: Link | None = None seasons: list[Link] | None = None - custom_season: Link | None = field(default=None, metadata=field_options(alias="customSeason")) + custom_season: Link | None = field( + default=None, metadata=field_options(alias="customSeason") + ) podcast: Link | None = None favourite: Link | None = None share: Link | None = None progress: Link | None = None progresses: list[Link] | None = None recommendations: Link | None = None - extra_material: Link | None = field(default=None, metadata=field_options(alias="extraMaterial")) - personalized_next: Link | None = field(default=None, metadata=field_options(alias="personalizedNext")) - user_data: Link | None = field(default=None, metadata=field_options(alias="userData")) + extra_material: Link | None = field( + default=None, metadata=field_options(alias="extraMaterial") + ) + personalized_next: Link | None = field( + default=None, metadata=field_options(alias="personalizedNext") + ) + user_data: Link | None = field( + default=None, metadata=field_options(alias="userData") + ) episodes: Link | None = None - highlighted_episode: Link | None = field(default=None, metadata=field_options(alias="highlightedEpisode")) + highlighted_episode: Link | None = field( + default=None, metadata=field_options(alias="highlightedEpisode") + ) @dataclass @@ -348,7 +434,9 @@ class ProgramInformation(BaseDataClassORJSONMixin): """Contains program information.""" details: ProgramInformationDetails - original_title: str | None = field(default=None, metadata=field_options(alias="originalTitle")) + original_title: str | None = field( + default=None, metadata=field_options(alias="originalTitle") + ) def __str__(self): if self.original_title is None: @@ -363,6 +451,13 @@ class Contributor(BaseDataClassORJSONMixin): role: str name: list[str] + @classmethod + def __pre_deserialize__(cls: type[T], d: T) -> T: + name = d.get("name", []) + if not isinstance(name, list): + d["name"] = [name] + return d + @dataclass class WebImage(BaseDataClassORJSONMixin): @@ -378,7 +473,9 @@ class Image(BaseDataClassORJSONMixin): url: str = field(metadata=field_options(alias="uri")) width: int | None = None - pixel_width: int | None = field(default=None, metadata=field_options(alias="pixelWidth")) + pixel_width: int | None = field( + default=None, metadata=field_options(alias="pixelWidth") + ) def __str__(self): return self.url @@ -389,10 +486,12 @@ class Duration(BaseDataClassORJSONMixin): """Represents the duration of the episode in various formats.""" seconds: int - iso8601: timedelta = field(metadata=field_options( - deserialize=parse_duration, - serialize=duration_isoformat, - )) + iso8601: timedelta = field( + metadata=field_options( + deserialize=parse_duration, + serialize=duration_isoformat, + ) + ) display_value: str = field(metadata=field_options(alias="displayValue")) @@ -401,15 +500,19 @@ class IndexPoint(BaseDataClassORJSONMixin): """Represents a point of interest within the episode.""" title: str - start_point: timedelta = field(metadata=field_options( - alias="startPoint", - deserialize=parse_duration, - serialize=duration_isoformat, - )) + start_point: timedelta = field( + metadata=field_options( + alias="startPoint", + deserialize=parse_duration, + serialize=duration_isoformat, + ) + ) description: str | None = None part_id: int | None = field(default=None, metadata=field_options(alias="partId")) mentioned: list[str] | None = None - subject_list: list[str] | None = field(default=None, metadata=field_options(alias="subjectList")) + subject_list: list[str] | None = field( + default=None, metadata=field_options(alias="subjectList") + ) contributors: list[Contributor] | None = None @@ -423,13 +526,17 @@ class PlaylistItem(BaseDataClassORJSONMixin): program_id: str = field(metadata=field_options(alias="programId")) channel_id: str = field(metadata=field_options(alias="channelId")) start_time: datetime = field(metadata=field_options(alias="startTime")) - duration: timedelta = field(metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat)) + duration: timedelta = field( + metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat) + ) program_title: str = field(metadata=field_options(alias="programTitle")) - start_point: timedelta = field(metadata=field_options( - alias="startPoint", - deserialize=parse_duration, - serialize=duration_isoformat, - )) + start_point: timedelta = field( + metadata=field_options( + alias="startPoint", + deserialize=parse_duration, + serialize=duration_isoformat, + ) + ) class Config(BaseConfig): discriminator = Discriminator( @@ -454,7 +561,9 @@ class Series(BaseDataClassORJSONMixin): type: PodcastType images: list[Image] square_images: list[Image] = field(metadata=field_options(alias="squareImages")) - season_id: str | None = field(default=None, metadata=field_options(alias="seasonId")) + season_id: str | None = field( + default=None, metadata=field_options(alias="seasonId") + ) @dataclass @@ -465,10 +574,14 @@ class Program(BaseDataClassORJSONMixin): id: str episode_id: str = field(metadata=field_options(alias="episodeId")) date: datetime - program_information: ProgramInformation = field(metadata=field_options(alias="programInformation")) + program_information: ProgramInformation = field( + metadata=field_options(alias="programInformation") + ) contributors: list[Contributor] image: list[Image] - temporal_titles: TemporalTitles = field(metadata=field_options(alias="temporalTitles")) + temporal_titles: TemporalTitles = field( + metadata=field_options(alias="temporalTitles") + ) availability: Availability category: Category usage_rights: UsageRights = field(metadata=field_options(alias="usageRights")) diff --git a/nrk_psapi/models/channels.py b/nrk_psapi/models/channels.py index 2ffd5f8..1ac6fa7 100644 --- a/nrk_psapi/models/channels.py +++ b/nrk_psapi/models/channels.py @@ -10,8 +10,10 @@ def parse_duration(time_str: str): - time_parts = time_str.split(':') - return timedelta(hours=int(time_parts[0]), minutes=int(time_parts[1]), seconds=int(time_parts[2])) + time_parts = time_str.split(":") + return timedelta( + hours=int(time_parts[0]), minutes=int(time_parts[1]), seconds=int(time_parts[2]) + ) def serialize_timedelta(duration: timedelta): @@ -38,10 +40,18 @@ class ChannelImage(BaseDataClassORJSONMixin): @dataclass class ChannelEntryImages(BaseDataClassORJSONMixin): - main_key_art_image: ChannelImage | None = field(default=None, metadata=field_options(alias="mainKeyArtImage")) - backdrop_image: ChannelImage | None = field(default=None, metadata=field_options(alias="backdropImage")) - poster_image: ChannelImage | None = field(default=None, metadata=field_options(alias="posterImage")) - square_image: ChannelImage | None = field(default=None, metadata=field_options(alias="squareImage")) + main_key_art_image: ChannelImage | None = field( + default=None, metadata=field_options(alias="mainKeyArtImage") + ) + backdrop_image: ChannelImage | None = field( + default=None, metadata=field_options(alias="backdropImage") + ) + poster_image: ChannelImage | None = field( + default=None, metadata=field_options(alias="posterImage") + ) + square_image: ChannelImage | None = field( + default=None, metadata=field_options(alias="squareImage") + ) @dataclass @@ -51,15 +61,19 @@ class ChannelEntry(BaseDataClassORJSONMixin): image: ChannelEntryImages actual_start: datetime = field(metadata=field_options(alias="actualStart")) actual_end: datetime = field(metadata=field_options(alias="actualEnd")) - program_duration: timedelta = field(metadata=field_options( - alias="programDuration", - deserialize=parse_duration, - serialize=serialize_timedelta, - )) - duration: timedelta = field(metadata=field_options( - deserialize=parse_duration, - serialize=serialize_timedelta, - )) + program_duration: timedelta = field( + metadata=field_options( + alias="programDuration", + deserialize=parse_duration, + serialize=serialize_timedelta, + ) + ) + duration: timedelta = field( + metadata=field_options( + deserialize=parse_duration, + serialize=serialize_timedelta, + ) + ) series_id: str | None = None @@ -76,14 +90,18 @@ class Channel(BaseDataClassORJSONMixin): id: str title: str type: ChannelType - live_buffer_duration: timedelta = field(metadata=field_options( - alias="liveBufferDuration", - deserialize=parse_duration, - serialize=serialize_timedelta, - )) + live_buffer_duration: timedelta = field( + metadata=field_options( + alias="liveBufferDuration", + deserialize=parse_duration, + serialize=serialize_timedelta, + ) + ) image: ChannelImage entries: list[ChannelEntry] - district_channel: DistrictChannel | None = field(default=None, metadata=field_options(alias="districtChannel")) + district_channel: DistrictChannel | None = field( + default=None, metadata=field_options(alias="districtChannel") + ) @dataclass diff --git a/nrk_psapi/models/common.py b/nrk_psapi/models/common.py index 42d3a3d..7405b5c 100644 --- a/nrk_psapi/models/common.py +++ b/nrk_psapi/models/common.py @@ -14,13 +14,18 @@ T = TypeVar("T", bound="DataClassORJSONMixin") + @dataclass class IpCheck(DataClassORJSONMixin): client_ip_address: str = field(metadata=field_options(alias="clientIpAddress")) country_code: str = field(metadata=field_options(alias="countryCode")) is_ip_norwegian: bool = field(metadata=field_options(alias="isIpNorwegian")) - lookup_source: IpCheckLookupSource = field(metadata=field_options(alias="lookupSource")) - access_group: IpCheckAccessGroup = field(metadata=field_options(alias="accessGroup")) + lookup_source: IpCheckLookupSource = field( + metadata=field_options(alias="lookupSource") + ) + access_group: IpCheckAccessGroup = field( + metadata=field_options(alias="accessGroup") + ) @dataclass diff --git a/nrk_psapi/models/metadata.py b/nrk_psapi/models/metadata.py index af7c4bf..c3a4ab4 100644 --- a/nrk_psapi/models/metadata.py +++ b/nrk_psapi/models/metadata.py @@ -7,7 +7,7 @@ from mashumaro import field_options from .catalog import Image, IndexPoint, Link, Links, Titles # noqa: TCH001 -from .common import BaseDataClassORJSONMixin +from .common import BaseDataClassORJSONMixin, T from .playback import AvailabilityDetailed, Playable # noqa: TCH001 @@ -99,17 +99,6 @@ class PodcastEpisodeMetadata(BaseDataClassORJSONMixin): clip_id: str | None = field(default=None, metadata=field_options(alias="clipId")) -@dataclass -class Embedded(BaseDataClassORJSONMixin): - """Represents the _embedded section in the API response.""" - - manifests: list[Manifest] - podcast: PodcastMetadataEmbedded - podcast_episode: PodcastEpisodeMetadata = field(metadata=field_options(alias="podcastEpisode")) - next: dict | None = None - previous: dict | None = None - - @dataclass class PodcastMetadata(BaseDataClassORJSONMixin): """Represents the main structure of the API response for podcast metadata.""" @@ -118,24 +107,35 @@ class PodcastMetadata(BaseDataClassORJSONMixin): id: str playability: str streaming_mode: str = field(metadata=field_options(alias="streamingMode")) - duration: timedelta = field(metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat)) + duration: timedelta = field( + metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat) + ) legal_age: LegalAge = field(metadata=field_options(alias="legalAge")) availability: AvailabilityDetailed preplay: Preplay playable: Playable source_medium: str = field(metadata=field_options(alias="sourceMedium")) - _embedded: Embedded - display_aspect_ratio: str | None = field(default=None, metadata=field_options(alias="displayAspectRatio")) - non_playable: dict | None = field(default=None, metadata=field_options(alias="nonPlayable")) - interaction_points: list | None = field(default=None, metadata=field_options(alias="interactionPoints")) - skip_dialog_info: dict | None = field(default=None, metadata=field_options(alias="skipDialogInfo")) + display_aspect_ratio: str | None = field( + default=None, metadata=field_options(alias="displayAspectRatio") + ) + non_playable: dict | None = field( + default=None, metadata=field_options(alias="nonPlayable") + ) + interaction_points: list | None = field( + default=None, metadata=field_options(alias="interactionPoints") + ) + skip_dialog_info: dict | None = field( + default=None, metadata=field_options(alias="skipDialogInfo") + ) interaction: dict | None = None - manifests: list[Manifest] = field(init=False) - podcast: PodcastMetadataEmbedded = field(init=False) - podcast_episode: PodcastEpisodeMetadata = field(init=False) + manifests: list[Manifest] = field(default_factory=list) + podcast: PodcastMetadataEmbedded | None = field(default=None) + podcast_episode: PodcastEpisodeMetadata | None = field(default=None) - def __post_init__(self): - self.manifests = self._embedded.manifests - self.podcast = self._embedded.podcast - self.podcast_episode = self._embedded.podcast_episode + @classmethod + def __pre_deserialize__(cls: type[T], d: T) -> T: + d["manifests"] = d.get("_embedded", {}).get("manifests") + d["podcast"] = d.get("_embedded", {}).get("podcast") + d["podcast_episode"] = d.get("_embedded", {}).get("podcastEpisode") + return d diff --git a/nrk_psapi/models/pages.py b/nrk_psapi/models/pages.py index 90364c3..2155660 100644 --- a/nrk_psapi/models/pages.py +++ b/nrk_psapi/models/pages.py @@ -18,29 +18,29 @@ class DisplayType(StrEnum): - DEFAULT = 'default' - GRID = 'grid' + DEFAULT = "default" + GRID = "grid" # noinspection SpellCheckingInspection class DisplayContract(StrEnum): - HERO = 'hero' - EDITORIAL = 'editorial' - INLINEHERO = 'inlineHero' - LANDSCAPE = 'landscape' - LANDSCAPELOGO = 'landscapeLogo' - SIMPLE = 'simple' - SQUARED = 'squared' - SQUAREDLOGO = 'squaredLogo' - NYHETSATOM = 'nyhetsAtom' - RADIOMULTIHERO = 'radioMultiHero' - SIDEKICKLOGO = 'sidekickLogo' + HERO = "hero" + EDITORIAL = "editorial" + INLINEHERO = "inlineHero" + LANDSCAPE = "landscape" + LANDSCAPELOGO = "landscapeLogo" + SIMPLE = "simple" + SQUARED = "squared" + SQUAREDLOGO = "squaredLogo" + NYHETSATOM = "nyhetsAtom" + RADIOMULTIHERO = "radioMultiHero" + SIDEKICKLOGO = "sidekickLogo" class PlugSize(StrEnum): - SMALL = 'small' - MEDIUM = 'medium' - LARGE = 'large' + SMALL = "small" + MEDIUM = "medium" + LARGE = "large" class PlugType(StrEnum): @@ -61,8 +61,8 @@ class SectionType(StrEnum): class PageTypeEnum(StrEnum): - CATEGORY = 'category' - SUBCATEGORY = 'subcategory' + CATEGORY = "category" + SUBCATEGORY = "subcategory" @dataclass @@ -117,7 +117,9 @@ class SectionEcommerce(BaseDataClassORJSONMixin): list: str variant: str category: str - product_custom_dimensions: ProductCustomDimensions = field(metadata=field_options(alias="productCustomDimensions")) + product_custom_dimensions: ProductCustomDimensions = field( + metadata=field_options(alias="productCustomDimensions") + ) @dataclass @@ -237,7 +239,9 @@ class PlaceholderSection(Section): type = SectionType.PLACEHOLDER placeholder: Placeholder id: str | None = None - e_commerce: SectionEcommerce | None = field(default=None, metadata=field_options(alias="eCommerce")) + e_commerce: SectionEcommerce | None = field( + default=None, metadata=field_options(alias="eCommerce") + ) @dataclass @@ -245,7 +249,9 @@ class PluggedEpisode(BaseDataClassORJSONMixin): title: str = field(init=False) titles: Titles image: WebImage - duration: timedelta = field(metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat)) + duration: timedelta = field( + metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat) + ) series: PluggedSeries | None = None def __post_init__(self): @@ -257,7 +263,9 @@ class PluggedSeries(BaseDataClassORJSONMixin): title: str = field(init=False) titles: Titles image: WebImage | None = None - number_of_episodes: int | None = field(default=None, metadata=field_options(alias="numberOfEpisodes")) + number_of_episodes: int | None = field( + default=None, metadata=field_options(alias="numberOfEpisodes") + ) def __post_init__(self): self.title = self.titles.title @@ -278,7 +286,9 @@ class PluggedStandaloneProgram(BaseDataClassORJSONMixin): title: str = field(init=False) titles: Titles image: WebImage - duration: timedelta = field(metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat)) + duration: timedelta = field( + metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat) + ) def __post_init__(self): self.title = self.titles.title @@ -288,8 +298,12 @@ def __post_init__(self): class PluggedPodcast(BaseDataClassORJSONMixin): podcast_title: str = field(init=False) titles: Titles - image_url: str | None = field(default=None, metadata=field_options(alias="imageUrl")) - number_of_episodes: int | None = field(default=None, metadata=field_options(alias="numberOfEpisodes")) + image_url: str | None = field( + default=None, metadata=field_options(alias="imageUrl") + ) + number_of_episodes: int | None = field( + default=None, metadata=field_options(alias="numberOfEpisodes") + ) def __post_init__(self): self.podcast_title = self.titles.title @@ -299,7 +313,9 @@ def __post_init__(self): class PluggedPodcastEpisode(BaseDataClassORJSONMixin): title: str = field(init=False) titles: Titles - duration: timedelta = field(metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat)) + duration: timedelta = field( + metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat) + ) image_url: str = field(metadata=field_options(alias="imageUrl")) podcast: PluggedPodcast podcast_title: str = field(init=False) @@ -312,13 +328,27 @@ def __post_init__(self): @dataclass class PluggedPodcastSeason(BaseDataClassORJSONMixin): _links: PodcastSeasonLinks | None = None - podcast_id: str | None = field(default=None, metadata=field_options(alias="podcastId")) - season_id: str | None = field(default=None, metadata=field_options(alias="seasonId")) - season_number: int | None = field(default=None, metadata=field_options(alias="seasonNumber")) - number_of_episodes: int | None = field(default=None, metadata=field_options(alias="numberOfEpisodes")) - image_url: str | None = field(default=None, metadata=field_options(alias="imageUrl")) - podcast_title: str | None = field(default=None, metadata=field_options(alias="podcastTitle")) - podcast_season_title: str | None = field(default=None, metadata=field_options(alias="podcastSeasonTitle")) + podcast_id: str | None = field( + default=None, metadata=field_options(alias="podcastId") + ) + season_id: str | None = field( + default=None, metadata=field_options(alias="seasonId") + ) + season_number: int | None = field( + default=None, metadata=field_options(alias="seasonNumber") + ) + number_of_episodes: int | None = field( + default=None, metadata=field_options(alias="numberOfEpisodes") + ) + image_url: str | None = field( + default=None, metadata=field_options(alias="imageUrl") + ) + podcast_title: str | None = field( + default=None, metadata=field_options(alias="podcastTitle") + ) + podcast_season_title: str | None = field( + default=None, metadata=field_options(alias="podcastSeasonTitle") + ) @dataclass @@ -341,7 +371,9 @@ class PageListItem(BaseDataClassORJSONMixin): title: str id: str | None = None image: WebImage | None = None - image_square: WebImage | None = field(default=None, metadata=field_options(alias="imageSquare")) + image_square: WebImage | None = field( + default=None, metadata=field_options(alias="imageSquare") + ) @dataclass @@ -358,10 +390,12 @@ class ChannelPlug(Plug): channel: PluggedChannel def __post_init__(self): - self.id = self._links.channel.split('/').pop() + self.id = self._links.channel.split("/").pop() # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) @@ -378,11 +412,13 @@ class SeriesPlug(Plug): series: PluggedSeries def __post_init__(self): - self.id = self._links.series.split('/').pop() + self.id = self._links.series.split("/").pop() self.title = self.series.title # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) @@ -402,12 +438,14 @@ class EpisodePlug(Plug): episode: PluggedEpisode def __post_init__(self): - self.id = self._links.episode.split('/').pop() - self.series_id = self._links.series.split('/').pop() + self.id = self._links.episode.split("/").pop() + self.series_id = self._links.series.split("/").pop() self.title = self.episode.title # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) @@ -424,10 +462,12 @@ class StandaloneProgramPlug(Plug): program: PluggedStandaloneProgram def __post_init__(self): - self.id = self._links.program.split('/').pop() + self.id = self._links.program.split("/").pop() # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) @@ -451,13 +491,17 @@ def __post_init__(self): self.tagline = self.podcast.titles.subtitle # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) table.add_row("title", self.title) table.add_row("tagline", self.tagline) - table.add_row("podcast.number_of_episodes", str(self.podcast.number_of_episodes)) + table.add_row( + "podcast.number_of_episodes", str(self.podcast.number_of_episodes) + ) yield table @@ -466,15 +510,19 @@ class PodcastEpisodePlug(Plug): id: str = field(init=False) podcast_id: str = field(init=False) type = PlugType.PODCAST_EPISODE - podcast_episode: PluggedPodcastEpisode = field(metadata=field_options(alias="podcastEpisode")) + podcast_episode: PluggedPodcastEpisode = field( + metadata=field_options(alias="podcastEpisode") + ) _links: PodcastEpisodePlugLinks def __post_init__(self): - self.id = self._links.podcast_episode.split('/').pop() - self.podcast_id = self._links.podcast.split('/').pop() + self.id = self._links.podcast_episode.split("/").pop() + self.podcast_id = self._links.podcast.split("/").pop() # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) @@ -487,11 +535,15 @@ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderR class PodcastSeasonPlug(Plug): type = PlugType.PODCAST_SEASON id: str - podcast_season: PluggedPodcastSeason = field(metadata=field_options(alias="podcastSeason")) + podcast_season: PluggedPodcastSeason = field( + metadata=field_options(alias="podcastSeason") + ) image: WebImage | None = None # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) @@ -507,7 +559,9 @@ class LinkPlug(Plug): image: WebImage | None = None # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) @@ -523,7 +577,9 @@ class PagePlug(Plug): image: WebImage | None = None # noinspection PyUnusedLocal - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: yield f"[b]{self.type}[/b]" table = Table("Attribute", "Value") table.add_row("id", self.id) diff --git a/nrk_psapi/models/playback.py b/nrk_psapi/models/playback.py index 43f9468..9d57393 100644 --- a/nrk_psapi/models/playback.py +++ b/nrk_psapi/models/playback.py @@ -27,7 +27,9 @@ class AvailabilityDetailed(BaseDataClassORJSONMixin): information: str is_geo_blocked: bool = field(metadata=field_options(alias="isGeoBlocked")) on_demand: OnDemand = field(metadata=field_options(alias="onDemand")) - external_embedding_allowed: bool = field(metadata=field_options(alias="externalEmbeddingAllowed")) + external_embedding_allowed: bool = field( + metadata=field_options(alias="externalEmbeddingAllowed") + ) live: dict[str, str] | None = None @@ -114,8 +116,9 @@ class Statistics(BaseDataClassORJSONMixin): ga: GaStatistics | None = None conviva: dict | None = None luna: Luna | None = None - quality_of_experience: QualityOfExperience | None = field(default=None, - metadata=field_options(alias="qualityOfExperience")) + quality_of_experience: QualityOfExperience | None = field( + default=None, metadata=field_options(alias="qualityOfExperience") + ) snowplow: dict[str, str] = field(default_factory=dict) @@ -136,13 +139,20 @@ def __str__(self) -> str: class Playable(BaseDataClassORJSONMixin): """Represents the playable content information.""" - end_sequence_start_time: str | None = field(default=None, metadata=field_options(alias="endSequenceStartTime")) - duration: timedelta | None = field(default=None, metadata=field_options( - deserialize=parse_duration, - serialize=duration_isoformat, - )) + end_sequence_start_time: str | None = field( + default=None, metadata=field_options(alias="endSequenceStartTime") + ) + duration: timedelta | None = field( + default=None, + metadata=field_options( + deserialize=parse_duration, + serialize=duration_isoformat, + ), + ) assets: list[Asset] | None = None - live_buffer: dict | None = field(default=None, metadata=field_options(alias="liveBuffer")) + live_buffer: dict | None = field( + default=None, metadata=field_options(alias="liveBuffer") + ) subtitles: list | None = None thumbnails: list | None = None resolve: str | None = None @@ -153,9 +163,15 @@ def __str__(self) -> str: @dataclass class SkipDialogInfo(BaseDataClassORJSONMixin): - start_intro_in_seconds: float = field(metadata=field_options(alias="startIntroInSeconds")) - end_intro_in_seconds: float = field(metadata=field_options(alias="endIntroInSeconds")) - start_credits_in_seconds: float = field(metadata=field_options(alias="startCreditsInSeconds")) + start_intro_in_seconds: float = field( + metadata=field_options(alias="startIntroInSeconds") + ) + end_intro_in_seconds: float = field( + metadata=field_options(alias="endIntroInSeconds") + ) + start_credits_in_seconds: float = field( + metadata=field_options(alias="startCreditsInSeconds") + ) start_intro: str = field(metadata=field_options(alias="startIntro")) end_intro: str = field(metadata=field_options(alias="endIntro")) start_credits: str = field(metadata=field_options(alias="startCredits")) @@ -168,12 +184,21 @@ class PodcastManifest(BaseDataClassORJSONMixin): _links: Links id: str playability: str - streaming_mode: PlayableStreamingMode = field(metadata=field_options(alias="streamingMode")) + streaming_mode: PlayableStreamingMode = field( + metadata=field_options(alias="streamingMode") + ) availability: AvailabilityDetailed statistics: Statistics playable: Playable - source_medium: PlayableSourceMedium = field(metadata=field_options(alias="sourceMedium")) - non_playable: dict | None = field(default=None, metadata=field_options(alias="nonPlayable")) - display_aspect_ratio: DisplayAspectRatioVideo | None = field(default=None, - metadata=field_options(alias="displayAspectRatio")) - skip_dialog_info: SkipDialogInfo | None = field(default=None, metadata=field_options(alias="skipDialogInfo")) + source_medium: PlayableSourceMedium = field( + metadata=field_options(alias="sourceMedium") + ) + non_playable: dict | None = field( + default=None, metadata=field_options(alias="nonPlayable") + ) + display_aspect_ratio: DisplayAspectRatioVideo | None = field( + default=None, metadata=field_options(alias="displayAspectRatio") + ) + skip_dialog_info: SkipDialogInfo | None = field( + default=None, metadata=field_options(alias="skipDialogInfo") + ) diff --git a/nrk_psapi/models/recommendations.py b/nrk_psapi/models/recommendations.py index 96cfbc7..03f7a84 100644 --- a/nrk_psapi/models/recommendations.py +++ b/nrk_psapi/models/recommendations.py @@ -22,6 +22,7 @@ class RecommendationType(str, Enum): def __str__(self) -> str: return str(self.value) + @dataclass class UpstreamSystemInfoPayload(BaseDataClassORJSONMixin): id: str @@ -84,7 +85,9 @@ class RecommendedSeries(BaseDataClassORJSONMixin): titles: Titles image: WebImage number_of_episodes: int = field(metadata=field_options(alias="numberOfEpisodes")) - square_image: WebImage | None = field(default=None, metadata=field_options(alias="squareImage")) + square_image: WebImage | None = field( + default=None, metadata=field_options(alias="squareImage") + ) @dataclass @@ -92,15 +95,21 @@ class RecommendedProgram(BaseDataClassORJSONMixin): id: str titles: Titles image: WebImage - duration: timedelta = field(metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat)) - square_image: WebImage | None = field(default=None, metadata=field_options(alias="squareImage")) + duration: timedelta = field( + metadata=field_options(deserialize=parse_duration, serialize=duration_isoformat) + ) + square_image: WebImage | None = field( + default=None, metadata=field_options(alias="squareImage") + ) @dataclass class EmbeddedRecommendation(BaseDataClassORJSONMixin): _links: Links type: RecommendationType - upstream_system_info: UpstreamSystemInfo = field(metadata=field_options(alias="upstreamSystemInfo")) + upstream_system_info: UpstreamSystemInfo = field( + metadata=field_options(alias="upstreamSystemInfo") + ) class Config(BaseConfig): discriminator = Discriminator( @@ -118,7 +127,9 @@ class EmbeddedPodcastRecommendation(EmbeddedRecommendation): @dataclass class EmbeddedPodcastSeasonRecommendation(EmbeddedRecommendation): type = RecommendationType.PODCAST_SEASON - podcast_season: RecommendedPodcastSeason + podcast_season: RecommendedPodcastSeason = field( + metadata=field_options(alias="podcastSeason") + ) @dataclass @@ -140,5 +151,8 @@ class Recommendation(BaseDataClassORJSONMixin): default_factory=list, metadata=field_options( alias="_embedded", - deserialize=lambda x: [EmbeddedRecommendation.from_dict(d) for d in x["recommendations"]], - )) + deserialize=lambda x: [ + EmbeddedRecommendation.from_dict(d) for d in x["recommendations"] + ], + ), + ) diff --git a/nrk_psapi/models/search.py b/nrk_psapi/models/search.py index b3b6da7..532982c 100644 --- a/nrk_psapi/models/search.py +++ b/nrk_psapi/models/search.py @@ -12,8 +12,36 @@ from .common import BaseDataClassORJSONMixin, StrEnum SingleLetter = Literal[ - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Æ', 'Ø', 'Å', '#', + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "Æ", + "Ø", + "Å", + "#", ] @@ -56,12 +84,24 @@ class SearchResultLink(BaseDataClassORJSONMixin): class Links(BaseDataClassORJSONMixin): """Represents the _links object in the API response.""" - next_letter: Link | None = field(default=None, metadata=field_options(alias="nextLetter")) - next_page: Link | None = field(default=None, metadata=field_options(alias="nextPage")) - prev_letter: Link | None = field(default=None, metadata=field_options(alias="prevLetter")) - prev_page: Link | None = field(default=None, metadata=field_options(alias="prevPage")) - custom_season: Link | None = field(default=None, metadata=field_options(alias="customSeason")) - single_program: Link | None = field(default=None, metadata=field_options(alias="singleProgram")) + next_letter: Link | None = field( + default=None, metadata=field_options(alias="nextLetter") + ) + next_page: Link | None = field( + default=None, metadata=field_options(alias="nextPage") + ) + prev_letter: Link | None = field( + default=None, metadata=field_options(alias="prevLetter") + ) + prev_page: Link | None = field( + default=None, metadata=field_options(alias="prevPage") + ) + custom_season: Link | None = field( + default=None, metadata=field_options(alias="customSeason") + ) + single_program: Link | None = field( + default=None, metadata=field_options(alias="singleProgram") + ) next: Link | None = None prev: Link | None = None podcast: Link | None = None @@ -125,6 +165,7 @@ class Letter(BaseDataClassORJSONMixin): count: int link: str + # # @dataclass # class Image(BaseDataClassORJSONMixin): @@ -154,7 +195,9 @@ class SearchedSeries(BaseDataClassORJSONMixin): images: list[Image] square_images: list[Image] = field(metadata=field_options(alias="squareImages")) _links: Links - season_id: str | None = field(default=None, metadata=field_options(alias="seasonId")) + season_id: str | None = field( + default=None, metadata=field_options(alias="seasonId") + ) @dataclass @@ -262,7 +305,9 @@ class SearchResponseResultEpisode(SearchResponseResult): series_title: str = field(metadata=field_options(alias="seriesTitle")) date: datetime square_images: list[Image] = field(metadata=field_options(alias="images_1_1")) - season_id: str | None = field(default=None, metadata=field_options(alias="seasonId")) + season_id: str | None = field( + default=None, metadata=field_options(alias="seasonId") + ) @dataclass @@ -276,7 +321,9 @@ class SearchResponseResultCustomSeasonEpisode(SearchResponseResult): series_title: str = field(metadata=field_options(alias="seriesTitle")) date: datetime square_images: list[Image] = field(metadata=field_options(alias="images_1_1")) - season_id: str | None = field(default=None, metadata=field_options(alias="seasonId")) + season_id: str | None = field( + default=None, metadata=field_options(alias="seasonId") + ) @dataclass @@ -310,6 +357,8 @@ class SearchResponse(BaseDataClassORJSONMixin): count: int take_count: SearchResponseCounts = field(metadata=field_options(alias="takeCount")) - total_count: SearchResponseCounts = field(metadata=field_options(alias="totalCount")) + total_count: SearchResponseCounts = field( + metadata=field_options(alias="totalCount") + ) results: SearchResponseResults is_suggest_result: bool = field(metadata=field_options(alias="isSuggestResult"))