Skip to content

Commit

Permalink
Add paginator utility methods & minor bugfixes (#2)
Browse files Browse the repository at this point in the history
* Add paginator method for lazy iteration, fix some model bugs

* Update README

* Update paginator lazy iter method and add next/previous methods where possible

* Bump version to 1.1.0 for release
  • Loading branch information
novanai authored Jul 28, 2024
1 parent 8b54c34 commit 009ff31
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 28 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ An asynchronous Python 3.10+ Spotify Web API wrapper.
* **Documentation:** <https://aiospotifypy.readthedocs.io/en/stable/>
* **Examples**: <https://github.com/novanai/aiospotify.py/tree/master/examples>

## Installation

```bash
pip install -U aiospotify.py
```

## Getting Started

```py
Expand Down
2 changes: 1 addition & 1 deletion spotify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from spotify.oauth import *
from spotify.types import *

__version__ = "1.0.0"
__version__ = "1.1.0"

BASE_URL = "https://api.spotify.com/v1"

Expand Down
56 changes: 35 additions & 21 deletions spotify/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ async def request(

async with self.session.request(
method,
f"{spotify.BASE_URL}/{url}",
f"{spotify.BASE_URL}/{url}" if not url.startswith(spotify.BASE_URL) else url,
params=utils.process_dict(params) if params is not None else None,
json=utils.process_dict(json) if json is not None else None,
data=data if data is not None else None,
Expand All @@ -86,9 +86,6 @@ async def request(
) as r:
data = await r.content.read()

with open("./test.json", "w") as f:
json_.dump(json_.loads(data), f, indent=4)

if r.content_type == "application/json" and r.ok:
return data
elif r.content_type == "application/json":
Expand Down Expand Up @@ -218,7 +215,7 @@ async def get_album_tracks(
params={"limit": limit, "offset": offset, "market": market},
)
assert tracks is not None
return models.Paginator[models.SimpleTrack].model_validate_json(tracks)
return models.Paginator[models.SimpleTrack].from_payload(tracks, self, models.SimpleTrack)

@validator
async def get_users_saved_albums(
Expand Down Expand Up @@ -253,7 +250,7 @@ async def get_users_saved_albums(
params={"limit": limit, "offset": offset, "market": market},
)
assert albums is not None
return models.Paginator[models.SavedAlbum].model_validate_json(albums)
return models.Paginator[models.SavedAlbum].from_payload(albums, self, models.SavedAlbum)

@validator
async def save_albums_for_current_user(
Expand Down Expand Up @@ -415,7 +412,7 @@ async def get_artists_albums(
},
)
assert albums is not None
return models.Paginator[models.ArtistAlbum].model_validate_json(albums)
return models.Paginator[models.ArtistAlbum].from_payload(albums, self, models.ArtistAlbum)

@validator
async def get_artists_top_tracks(
Expand Down Expand Up @@ -544,7 +541,9 @@ async def get_audiobook_chapters(
params={"limit": limit, "offset": offset, "market": market},
)
assert chapters is not None
return models.Paginator[models.SimpleChapter].model_validate_json(chapters)
return models.Paginator[models.SimpleChapter].from_payload(
chapters, self, models.SimpleChapter
)

@validator
async def get_users_saved_audiobooks(
Expand Down Expand Up @@ -572,7 +571,9 @@ async def get_users_saved_audiobooks(
"""
audiobooks = await self.get("me/audiobooks", params={"limit": limit, "offset": offset})
assert audiobooks is not None
return models.Paginator[models.SimpleAudiobook].model_validate_json(audiobooks)
return models.Paginator[models.SimpleAudiobook].from_payload(
audiobooks, self, models.SimpleAudiobook
)

@validator
async def save_audiobooks_for_user(
Expand Down Expand Up @@ -903,7 +904,9 @@ async def get_users_saved_episodes(
params={"limit": limit, "offset": offset, "market": market},
)
assert episodes is not None
return models.Paginator[models.SavedEpisode].model_validate_json(episodes)
return models.Paginator[models.SavedEpisode].from_payload(
episodes, self, models.SavedEpisode
)

@validator
async def save_episodes_for_current_user(
Expand Down Expand Up @@ -1355,7 +1358,8 @@ async def get_recently_played_tracks(
after: MissingOr[datetime.datetime] = MISSING,
before: MissingOr[datetime.datetime] = MISSING,
) -> models.CursorPaginator[models.PlayHistory]:
"""Get tracks from the current user's recently played tracks.
"""Get tracks from the current user's recently played tracks, starting with the most recently
played track and going backwards in history.
!!! scopes "Required Authorization Scope"
[`USER_READ_RECENTLY_PLAYED`][spotify.enums.Scope.USER_READ_RECENTLY_PLAYED]
Expand Down Expand Up @@ -1387,7 +1391,9 @@ async def get_recently_played_tracks(
},
)
assert played is not None
return models.CursorPaginator[models.PlayHistory].model_validate_json(played)
return models.CursorPaginator[models.PlayHistory].from_payload(
played, self, models.PlayHistory
)

@validator
async def get_users_queue(self) -> models.Queue:
Expand Down Expand Up @@ -1579,7 +1585,7 @@ async def get_playlist_items(
},
)
assert items is not None
return models.Paginator[models.PlaylistItem].model_validate_json(items)
return models.Paginator[models.PlaylistItem].from_payload(items, self, models.PlaylistItem)

# TODO: split replace and reorder into their own overloads
@validator
Expand Down Expand Up @@ -1760,7 +1766,9 @@ async def get_current_users_playlists(
},
)
assert playlists is not None
return models.Paginator[models.SimplePlaylist].model_validate_json(playlists)
return models.Paginator[models.SimplePlaylist].from_payload(
playlists, self, models.SimplePlaylist
)

@validator
async def get_users_playlists(
Expand Down Expand Up @@ -1800,7 +1808,9 @@ async def get_users_playlists(
},
)
assert playlists is not None
return models.Paginator[models.SimplePlaylist].model_validate_json(playlists)
return models.Paginator[models.SimplePlaylist].from_payload(
playlists, self, models.SimplePlaylist
)

@validator
async def create_playlist(
Expand Down Expand Up @@ -2200,7 +2210,9 @@ async def get_show_episodes(
params={"limit": limit, "offset": offset, "market": market},
)
assert episodes is not None
return models.Paginator[models.SimpleEpisode].model_validate_json(episodes)
return models.Paginator[models.SimpleEpisode].from_payload(
episodes, self, models.SimpleEpisode
)

@validator
async def get_users_saved_shows(
Expand Down Expand Up @@ -2231,7 +2243,7 @@ async def get_users_saved_shows(
params={"limit": limit, "offset": offset},
)
assert shows is not None
return models.Paginator[models.SavedShow].model_validate_json(shows)
return models.Paginator[models.SavedShow].from_payload(shows, self, models.SavedShow)

@validator
async def save_shows_for_current_user(
Expand Down Expand Up @@ -2369,7 +2381,7 @@ async def get_users_saved_tracks(
params={"limit": limit, "offset": offset, "market": market},
)
assert tracks is not None
return models.Paginator[models.SavedTrack].model_validate_json(tracks)
return models.Paginator[models.SavedTrack].from_payload(tracks, self, models.SavedTrack)

@validator
async def save_tracks_for_current_user(
Expand Down Expand Up @@ -2788,10 +2800,12 @@ async def get_users_top_items(
)
assert items is not None
if type is enums.TopItemType.ARTISTS:
return models.Paginator[models.Artist].model_validate_json(items)
return models.Paginator[models.Artist].from_payload(items, self, models.Artist)
else:
assert type is enums.TopItemType.TRACKS
return models.Paginator[models.TrackWithSimpleArtist].model_validate_json(items)
return models.Paginator[models.TrackWithSimpleArtist].from_payload(
items, self, models.TrackWithSimpleArtist
)

@validator
async def get_users_profile(
Expand Down Expand Up @@ -2885,7 +2899,7 @@ async def get_followed_artists(
params={"type": "artist", "after": after, "limit": limit},
)
assert followed is not None
return internals.ArtistsPaginator.model_validate_json(followed).paginator
return internals.ArtistsPaginator.from_payload(followed, self).paginator

@validator
async def follow_artists_or_users(
Expand Down
12 changes: 12 additions & 0 deletions spotify/internals.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from __future__ import annotations

import typing

import pydantic

from spotify import models

if typing.TYPE_CHECKING:
from spotify import api


class Albums(pydantic.BaseModel):
albums: list[models.Album]
Expand Down Expand Up @@ -60,6 +65,13 @@ class AudioFeatures(pydantic.BaseModel):
class ArtistsPaginator(pydantic.BaseModel):
paginator: models.CursorPaginator[models.Artist] = pydantic.Field(alias="artists")

@classmethod
def from_payload(cls, data: bytes, api_class: api.API) -> typing.Self:
obj = cls.model_validate_json(data)
obj.paginator._api = api_class # pyright: ignore[reportPrivateUsage]
obj.paginator._item_type = models.Artist # pyright: ignore[reportPrivateUsage]
return obj


class Devices(pydantic.BaseModel):
devices: list[models.Device]
Expand Down
Loading

0 comments on commit 009ff31

Please sign in to comment.