Полный релиз 1.0.0 #5

Merged
Miwory merged 1 commits from dev into latest 2026-04-02 07:59:52 +03:00
3 changed files with 1063 additions and 249 deletions

View File

@ -1,11 +1,11 @@
[project] [project]
name = "oxidespotify" name = "oxidespotify"
version = "0.2.2" version = "1.0.0"
description = "Client for Spotify API" description = "Client for Spotify API"
readme = "README.md" readme = "README.md"
authors = [{ name = "Miwory", email = "miwory.uwu@gmail.com" }] authors = [{ name = "Miwory", email = "miwory.uwu@gmail.com" }]
requires-python = ">=3.14" requires-python = ">=3.14"
dependencies = ["oxidehttp>=1.0.3,<=2.0.0", "pydantic>=2.12,<=2.13"] dependencies = ["oxidehttp>=1.4.0,<=2.0.0", "pydantic>=2.12,<=2.13"]
[build-system] [build-system]
requires = ["uv_build>=0.9.2,<0.11.0"] requires = ["uv_build>=0.9.2,<0.11.0"]

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
from datetime import datetime from datetime import datetime
from typing import Literal from typing import Literal
from pydantic import BaseModel, HttpUrl, field_validator from pydantic import BaseModel, HttpUrl, RootModel, field_validator
class Error(Exception): class Error(Exception):
@ -62,6 +62,20 @@ class Image(BaseModel):
return v 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
total: int
class ExternalUrls(BaseModel): class ExternalUrls(BaseModel):
spotify: HttpUrl spotify: HttpUrl
@ -72,6 +86,16 @@ class ExternalIDs(BaseModel):
upc: 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): class Artist(BaseModel):
external_urls: ExternalUrls external_urls: ExternalUrls
href: HttpUrl href: HttpUrl
@ -81,19 +105,28 @@ class Artist(BaseModel):
uri: str uri: str
class Cursor(BaseModel): class BaseTrack(BaseModel):
after: int artists: list[Artist]
before: int disc_number: int
duration_ms: int
explicit: bool
class Context(BaseModel):
type: Literal['artist', 'playlist', 'album', 'show', 'collection']
href: HttpUrl
external_urls: ExternalUrls 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 uri: str
is_local: bool
class Album(BaseModel): class AlbumTracks(Paginated):
items: list[BaseTrack]
class BaseAlbum(BaseModel):
album_type: Literal['album', 'single', 'compilation'] album_type: Literal['album', 'single', 'compilation']
total_tracks: int total_tracks: int
external_urls: ExternalUrls external_urls: ExternalUrls
@ -109,103 +142,27 @@ class Album(BaseModel):
artists: list[Artist] artists: list[Artist]
class PlaylistOwner(BaseModel): class Album(BaseAlbum):
external_urls: ExternalUrls
href: HttpUrl
id: str
type: Literal['user']
uri: str
display_name: str | None
class PlaylistTracks(BaseModel):
href: HttpUrl
total: int
class Playlist(BaseModel):
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
type: Literal['playlist']
uri: str
class Track(BaseModel):
album: Album
artists: list[Artist]
disc_number: int
duration_ms: int
explicit: bool
external_ids: ExternalIDs external_ids: ExternalIDs
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 EpisodeResumePoint(BaseModel):
fully_played: bool
resume_position_ms: int
class Copyright(BaseModel):
text: str
type: Literal['C', 'P']
class Show(BaseModel):
copyrights: list[Copyright] copyrights: list[Copyright]
description: str tracks: AlbumTracks
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(BaseModel): class Track(BaseTrack):
description: str album: BaseAlbum
html_description: str
duration_ms: int
explicit: bool class UserSavedAlbum(BaseModel):
external_urls: ExternalUrls added_at: datetime
href: HttpUrl album: Album
id: str
images: list[Image]
is_externally_hosted: bool class UserSavedAlbums(Paginated):
is_playable: bool items: list[UserSavedAlbum]
languages: list[str]
name: str
release_date: datetime class ArtistsAlbums(Paginated):
release_date_precision: Literal['year', 'month', 'day'] items: list[Album]
resume_point: EpisodeResumePoint
type: Literal['episode']
uri: str
restrictions: Restriction | None = None
show: Show | None = None
class AudioBookAuthor(BaseModel): class AudioBookAuthor(BaseModel):
@ -236,17 +193,79 @@ class AudioBook(BaseModel):
total_chapters: int total_chapters: int
class PlaybackStateActions(BaseModel): class AudioBookChapter(BaseModel):
interrupting_playback: bool | None = None chapter_number: int
pausing: bool | None = None description: str
resuming: bool | None = None html_description: str
seeking: bool | None = None duration_ms: int
skipping_next: bool | None = None explicit: bool
skipping_prev: bool | None = None external_urls: ExternalUrls
toggling_repeat_context: bool | None = None href: HttpUrl
toggling_shuffle: bool | None = None id: str
toggling_repeat_track: bool | None = None images: list[Image]
transferring_playback: bool | None = None is_playable: bool
languages: list[str]
name: str
release_date: datetime
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): class PlaybackState(BaseModel):
@ -259,35 +278,94 @@ class PlaybackState(BaseModel):
is_playing: bool is_playing: bool
item: Track | Episode | None item: Track | Episode | None
currently_playing_type: Literal['track', 'episode', 'ad', 'unknown'] currently_playing_type: Literal['track', 'episode', 'ad', 'unknown']
# TODO: allows has 'disallows' inside? not sure about that
# actions: PlaybackStateActions
class PlayHistoryObject(BaseModel): class Devices(BaseModel):
devices: list[Device]
class RecentlyPlayedTrack(BaseModel):
track: Track track: Track
played_at: datetime played_at: datetime
context: Context | None = None context: Context
class RecentlyPlayed(BaseModel): 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 href: HttpUrl
limit: int id: str
next: HttpUrl | None type: Literal['user']
cursors: Cursor uri: str
total: int | None = None display_name: str | None
items: list[PlayHistoryObject]
class SearchData(BaseModel): 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 href: HttpUrl
limit: int
next: HttpUrl | None
offset: int
previous: int | None
total: int total: int
class SearchAlbumData(SearchData): 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] items: list[Album]
@ -295,7 +373,7 @@ class SearchAlbum(BaseModel):
albums: SearchAlbumData albums: SearchAlbumData
class SearchArtistData(SearchData): class SearchArtistData(Paginated):
items: list[Artist] items: list[Artist]
@ -303,7 +381,7 @@ class SearchArtist(BaseModel):
artists: SearchArtistData artists: SearchArtistData
class SearchPlaylistData(SearchData): class SearchPlaylistData(Paginated):
items: list[Playlist | None] items: list[Playlist | None]
@ -311,7 +389,7 @@ class SearchPlaylist(BaseModel):
playlists: SearchPlaylistData playlists: SearchPlaylistData
class SearchTrackData(SearchData): class SearchTrackData(Paginated):
items: list[Track] items: list[Track]
@ -319,7 +397,7 @@ class SearchTrack(BaseModel):
tracks: SearchTrackData tracks: SearchTrackData
class SearchShowData(SearchData): class SearchShowData(Paginated):
items: list[Show] items: list[Show]
@ -327,7 +405,7 @@ class SearchShow(BaseModel):
shows: SearchShowData shows: SearchShowData
class SearchEpisodeData(SearchData): class SearchEpisodeData(Paginated):
items: list[Episode] items: list[Episode]
@ -335,7 +413,7 @@ class SearchEpisode(BaseModel):
episodes: SearchEpisodeData episodes: SearchEpisodeData
class SearchAudiobookData(SearchData): class SearchAudiobookData(Paginated):
items: list[AudioBook] items: list[AudioBook]
@ -343,6 +421,28 @@ class SearchAudiobook(BaseModel):
audiobooks: SearchAudiobookData 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): class UserProfile(BaseModel):
id: str id: str
display_name: str display_name: str
@ -351,3 +451,19 @@ class UserProfile(BaseModel):
images: list[Image] images: list[Image]
type: Literal['user'] type: Literal['user']
uri: str 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