Files
OxideSpotify/src/oxidespotify/schema.py
Miwory 3cbed95c08
All checks were successful
Verify Dev Build / publish (push) Successful in 25s
Фикс схемы недавних треков
2026-04-14 23:24:50 +03:00

491 lines
8.8 KiB
Python

from datetime import date, datetime
from typing import Annotated, Literal
from pydantic import (
BaseModel,
BeforeValidator,
HttpUrl,
RootModel,
field_validator,
)
class Error(Exception):
status_code: int
error: str
def __init__(self, status_code: int, error: str) -> None:
self.status_code = status_code
self.error = error
super().__init__(f'{status_code}: {error}')
class ClientError(Error):
pass
class InternalError(Error):
pass
def normalize_spotify_date(v: str) -> str:
parts = v.split('-')
if len(parts) == 1:
return f'{v}-01-01'
if len(parts) == 2:
return f'{v}-01'
return v
PrecisionedReleaseDate = Annotated[
date, BeforeValidator(normalize_spotify_date)
]
class Token(BaseModel):
token_type: Literal['Bearer']
access_token: str
expires_in: int
scope: str
class UserAccessToken(Token):
refresh_token: str
class Device(BaseModel):
id: str | None
is_active: bool
is_private_session: bool
is_restricted: bool
name: str
type: str
volume_percent: int | None
supports_volume: bool
class Restriction(BaseModel):
reason: Literal['market', 'product', 'explicit']
class Image(BaseModel):
url: HttpUrl
height: int
width: int
@field_validator('height', 'width', mode='before')
@classmethod
def convert_null_to_zero(cls, v: int | None) -> int:
if v is None:
return 0
return v
class Cursors(BaseModel):
after: int
before: int
class Paginated(BaseModel):
href: HttpUrl
limit: int
next: HttpUrl | None
offset: int = 0
previous: HttpUrl | None = None
total: int = 0
class ExternalUrls(BaseModel):
spotify: HttpUrl
class ExternalIDs(BaseModel):
isrc: str | None = None
ean: str | None = None
upc: str | None = None
class Copyright(BaseModel):
text: str
type: Literal['C', 'P']
class ResumePoint(BaseModel):
fully_played: bool
resume_position_ms: int
class Artist(BaseModel):
external_urls: ExternalUrls
href: HttpUrl
id: str
name: str
type: Literal['artist']
uri: str
class BaseTrack(BaseModel):
artists: list[Artist]
disc_number: int
duration_ms: int
explicit: bool
external_urls: ExternalUrls
href: HttpUrl
id: str
is_playable: bool | None = None
restrictions: Restriction | None = None
name: str
track_number: int
type: Literal['track']
uri: str
is_local: bool
class AlbumTracks(Paginated):
items: list[BaseTrack]
class BaseAlbum(BaseModel):
album_type: Literal['album', 'single', 'compilation']
total_tracks: int
external_urls: ExternalUrls
href: HttpUrl
id: str
images: list[Image]
name: str
release_date: PrecisionedReleaseDate
release_date_precision: Literal['year', 'month', 'day']
restrictions: Restriction | None = None
type: Literal['album']
uri: str
artists: list[Artist]
class Album(BaseAlbum):
external_ids: ExternalIDs
copyrights: list[Copyright]
tracks: AlbumTracks
class Track(BaseTrack):
album: BaseAlbum
class UserSavedAlbum(BaseModel):
added_at: datetime
album: Album
class UserSavedAlbums(Paginated):
items: list[UserSavedAlbum]
class ArtistsAlbums(Paginated):
items: list[Album]
class AudioBookAuthor(BaseModel):
name: str
class AudioBookNarator(BaseModel):
name: str
class AudioBook(BaseModel):
authors: list[AudioBookAuthor]
copyrights: list[Copyright]
description: str
html_description: str
edition: str
explicit: bool
external_urls: ExternalUrls
href: str
id: str
images: list[Image]
languages: list[str]
media_type: str
name: str
narrators: list[AudioBookNarator]
type: Literal['audiobook']
uri: str
total_chapters: int
class AudioBookChapter(BaseModel):
chapter_number: int
description: str
html_description: str
duration_ms: int
explicit: bool
external_urls: ExternalUrls
href: HttpUrl
id: str
images: list[Image]
is_playable: bool
languages: list[str]
name: str
release_date: PrecisionedReleaseDate
release_date_precision: Literal['year', 'month', 'day']
resume_point: ResumePoint
type: Literal['episode']
uri: str
restrictions: Restriction
class AudiobookChapters(BaseModel):
items: list[AudioBookChapter]
class UserSavedAudiobooks(Paginated):
items: list[AudioBook]
class Chapter(AudioBookChapter):
audiobook: AudioBook
class Show(BaseModel):
copyrights: list[Copyright]
description: str
html_description: str
explicit: bool
external_ids: ExternalIDs | None = None
href: HttpUrl
id: str
images: list[Image]
is_externally_hosted: bool
languages: list[str]
media_type: str
name: str
type: Literal['show']
uri: str
total_episodes: int
class Episode(AudioBookChapter):
show: Show
class UsedSavedEpisode(BaseModel):
added_at: datetime
episode: Episode
class UserSavedEpisodes(Paginated):
items: list[UsedSavedEpisode]
class CheckUserSavedItems(RootModel[list[bool]]):
pass
class Context(BaseModel):
type: Literal['artist', 'playlist', 'album', 'show', 'collection']
href: HttpUrl
external_urls: ExternalUrls
uri: str
class PlaybackState(BaseModel):
device: Device
repeat_state: Literal['off', 'track', 'context']
shuffle_state: bool
context: Context | None
timestamp: int
progress_ms: int
is_playing: bool
item: Track | Episode | None
currently_playing_type: Literal['track', 'episode', 'ad', 'unknown']
class Devices(BaseModel):
devices: list[Device]
class RecentlyPlayedTrack(BaseModel):
track: Track
played_at: datetime
context: Context | None = None
class RecentlyPlayedTracks(Paginated):
cursors: Cursors
items: list[RecentlyPlayedTrack]
class UsersQueue(BaseModel):
currently_playing: Track | Episode | None
queue: list[Track | Episode]
class PlaylistOwner(BaseModel):
external_urls: ExternalUrls
href: HttpUrl
id: str
type: Literal['user']
uri: str
display_name: str | None
class PlaylistTrack(BaseModel):
added_at: datetime
added_by: PlaylistOwner
is_local: bool
item: Track | Episode
class PlaylistTracks(Paginated):
href: HttpUrl
item: PlaylistTrack
class SimplifiedPlaylistTracks(Paginated):
href: HttpUrl
total: int = 0
class Playlist(BaseModel):
type: Literal['playlist']
uri: str
collaborative: bool
description: str
external_urls: ExternalUrls
href: HttpUrl
id: str
images: list[Image]
name: str
owner: PlaylistOwner
public: bool
snapshot_id: str
items: PlaylistTracks | None = None
class SimplifiedPlaylist(BaseModel):
type: Literal['playlist']
uri: str
collaborative: bool
external_urls: ExternalUrls
href: HttpUrl
id: str
images: list[Image]
name: str
owner: PlaylistOwner
public: bool
snapshot_id: str
items: SimplifiedPlaylistTracks
class UserPlaylists(Paginated):
items: list[Playlist]
class PlaylistImages(RootModel[list[Image]]):
pass
class SearchAlbumData(Paginated):
items: list[Album]
class SearchAlbum(BaseModel):
albums: SearchAlbumData
class SearchArtistData(Paginated):
items: list[Artist]
class SearchArtist(BaseModel):
artists: SearchArtistData
class SearchPlaylistData(Paginated):
items: list[Playlist | None]
class SearchPlaylist(BaseModel):
playlists: SearchPlaylistData
class SearchTrackData(Paginated):
items: list[Track]
class SearchTrack(BaseModel):
tracks: SearchTrackData
class SearchShowData(Paginated):
items: list[Show]
class SearchShow(BaseModel):
shows: SearchShowData
class SearchEpisodeData(Paginated):
items: list[Episode]
class SearchEpisode(BaseModel):
episodes: SearchEpisodeData
class SearchAudiobookData(Paginated):
items: list[AudioBook]
class SearchAudiobook(BaseModel):
audiobooks: SearchAudiobookData
class ShowEpisodes(Paginated):
items: list[AudioBookChapter]
class SavedShow(BaseModel):
added_at: datetime
show: Show
class SavedShows(Paginated):
items: list[SavedShow]
class SavedTrack(BaseModel):
added_at: datetime
track: Track
class SavedTracks(Paginated):
items: list[SavedTrack]
class UserProfile(BaseModel):
id: str
display_name: str
external_urls: ExternalUrls
href: HttpUrl
images: list[Image]
type: Literal['user']
uri: str
class UsersTopItemsTracks(Paginated):
items: list[Track]
class UsersTopItemsArtists(Paginated):
items: list[Artist]
class FollowedArtistsData(Paginated):
items: list[Artist]
class FollowedArtists(BaseModel):
artists: FollowedArtistsData