from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass from datetime import datetime from types import get_original_bases from typing import Generic, Self, TypeVar, get_args T = TypeVar('T') class _Api(Generic[T], ABC): @staticmethod @abstractmethod def from_dict(info: dict) -> T: raise NotImplementedError T_api = TypeVar('T_api', bound=_Api) # Generic Objects @dataclass class PageInfo(_Api): totalResults: int resultsPerPage: int @staticmethod def from_dict(info: dict) -> PageInfo: return PageInfo(totalResults=info['totalResults'], resultsPerPage=info['resultsPerPage']) @dataclass class ThumbnailInfo(_Api): url: str width: int height: int @staticmethod def from_dict(info: dict) -> ThumbnailInfo: return ThumbnailInfo(url=info['url'], width=info['width'], height=info['height']) @dataclass class Thumbnails(_Api): default: ThumbnailInfo medium: ThumbnailInfo high: ThumbnailInfo @staticmethod def from_dict(info: dict) -> Thumbnails: return Thumbnails( default=ThumbnailInfo.from_dict(info['default']), medium=ThumbnailInfo.from_dict(info['medium']), high=ThumbnailInfo.from_dict(info['high'])) @dataclass class Result(Generic[T_api]): kind: str etag: str nextPageToken: str | None pageInfo: PageInfo items: list[T_api] @classmethod def from_dict(cls, info: dict) -> Self: item_type = get_args(get_original_bases(cls)[0])[0] return cls( kind=info['kind'], etag=info['etag'], nextPageToken=info.get('nextPageToken'), pageInfo=PageInfo.from_dict(info['pageInfo']), items=[item_type.from_dict(i) for i in info['items']]) # Channel Objects @dataclass class ChannelSnippet(_Api): title: str description: str customUrl: str publishedAt: datetime thumbnails: Thumbnails country: str | None @staticmethod def from_dict(info: dict) -> ChannelSnippet: return ChannelSnippet( title=info['title'], description=info['description'], customUrl=info['customUrl'], publishedAt=datetime.fromisoformat(info['publishedAt']), thumbnails=Thumbnails.from_dict(info['thumbnails']), country=info.get('country')) @dataclass class ChannelResultItem(_Api): kind: str etag: str id: str snippet: ChannelSnippet @staticmethod def from_dict(info: dict) -> ChannelResultItem: return ChannelResultItem( kind=info['kind'], etag=info['etag'], id=info['id'], snippet=ChannelSnippet.from_dict(info['snippet'])) class ChannelResult(Result[ChannelResultItem]): pass # Search Objects @dataclass class SearchResultId(_Api): kind: str videoId: str @staticmethod def from_dict(info: dict) -> SearchResultId: return SearchResultId(kind=info['kind'], videoId=info['videoId']) @dataclass class SearchSnippet(_Api): publishedAt: datetime channelId: str title: str description: str thumbnails: Thumbnails channelTitle: str liveBroadcastContent: str publishTime: datetime @staticmethod def from_dict(info: dict) -> SearchSnippet: return SearchSnippet( publishedAt=datetime.fromisoformat(info['publishedAt']), channelId=info['channelId'], title=info['title'], description=info['description'], thumbnails=Thumbnails.from_dict(info['thumbnails']), channelTitle=info['channelTitle'], liveBroadcastContent=info['liveBroadcastContent'], publishTime=datetime.fromisoformat(info['publishTime'])) @dataclass class SearchResultItem(_Api): kind: str etag: str id: SearchResultId snippet: SearchSnippet @staticmethod def from_dict(info: dict) -> SearchResultItem: return SearchResultItem( kind=info['kind'], etag=info['etag'], id=SearchResultId.from_dict(info['id']), snippet=SearchSnippet.from_dict(info['snippet'])) class SearchResult(Result[SearchResultItem]): pass