Skip to content

Commit 7707886

Browse files
authored
Add method to tracks (#492)
1 parent 564fabc commit 7707886

File tree

4 files changed

+165
-9
lines changed

4 files changed

+165
-9
lines changed

src/spotifyaio/spotify.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -793,24 +793,49 @@ async def get_saved_tracks(self) -> list[SavedTrack]:
793793
response = await self._get("v1/me/tracks", params=params)
794794
return SavedTrackResponse.from_json(response).items
795795

796-
# Save a track
797-
798-
# Remove a track
796+
async def save_tracks(self, track_ids: list[str]) -> None:
797+
"""Save tracks."""
798+
if not track_ids:
799+
return
800+
if len(track_ids) > 50:
801+
msg = "Maximum of 50 tracks can be saved at once"
802+
raise ValueError(msg)
803+
params: dict[str, Any] = {
804+
"ids": ",".join([get_identifier(i) for i in track_ids])
805+
}
806+
await self._put("v1/me/tracks", params=params)
799807

800-
# Check if one or more tracks is already saved
808+
async def remove_saved_tracks(self, track_ids: list[str]) -> None:
809+
"""Remove saved tracks."""
810+
if not track_ids:
811+
return
812+
if len(track_ids) > 50:
813+
msg = "Maximum of 50 tracks can be removed at once"
814+
raise ValueError(msg)
815+
params: dict[str, Any] = {
816+
"ids": ",".join([get_identifier(i) for i in track_ids])
817+
}
818+
await self._delete("v1/me/tracks", params=params)
801819

802-
# Get audio features for several tracks
820+
async def are_tracks_saved(self, track_ids: list[str]) -> dict[str, bool]:
821+
"""Check if tracks are saved."""
822+
if not track_ids:
823+
return {}
824+
if len(track_ids) > 50:
825+
msg = "Maximum of 50 tracks can be checked at once"
826+
raise ValueError(msg)
827+
identifiers = [get_identifier(i) for i in track_ids]
828+
params: dict[str, Any] = {"ids": ",".join(identifiers)}
829+
response = await self._get("v1/me/tracks/contains", params=params)
830+
body: list[bool] = orjson.loads(response) # pylint: disable=no-member
831+
return dict(zip(identifiers, body))
803832

804833
async def get_audio_features(self, track_id: str) -> AudioFeatures:
805834
"""Get audio features."""
806835
identifier = get_identifier(track_id)
807836
response = await self._get(f"v1/audio-features/{identifier}")
808837
return AudioFeatures.from_json(response)
809838

810-
# Get audio analysis for a track
811-
812-
# Get recommendations
813-
814839
async def get_current_user(self) -> UserProfile:
815840
"""Get current user."""
816841
response = await self._get("v1/me")

tests/__snapshots__/test_spotify.ambr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
'1HGw3J3NxZO1TP1BTtVhpZ': False,
1919
})
2020
# ---
21+
# name: test_check_saved_tracks
22+
dict({
23+
'18yVqkdbdRvS24c0Ilj2ci': True,
24+
'1HGw3J3NxZO1TP1BTtVhpZ': False,
25+
})
26+
# ---
2127
# name: test_checking_saved_albums
2228
dict({
2329
'1A2GTWGtFfWp7KSQTwWOyo': False,

tests/fixtures/tracks_saved.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
true,
3+
false
4+
]

tests/test_spotify.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2366,3 +2366,124 @@ async def test_check_too_many_saved_shows(
23662366
with pytest.raises(ValueError, match="Maximum of 50 shows can be checked at once"):
23672367
await authenticated_client.are_shows_saved(["abc"] * 51)
23682368
responses.assert_not_called() # type: ignore[no-untyped-call]
2369+
2370+
2371+
async def test_save_tracks(
2372+
responses: aioresponses,
2373+
authenticated_client: SpotifyClient,
2374+
) -> None:
2375+
"""Test saving tracks."""
2376+
responses.put(
2377+
f"{SPOTIFY_URL}/v1/me/tracks?ids=0TnOYISbd1XYRBk9myaseg",
2378+
status=200,
2379+
body="",
2380+
)
2381+
await authenticated_client.save_tracks(["0TnOYISbd1XYRBk9myaseg"])
2382+
responses.assert_called_once_with(
2383+
f"{SPOTIFY_URL}/v1/me/tracks",
2384+
METH_PUT,
2385+
headers=HEADERS,
2386+
params={"ids": "0TnOYISbd1XYRBk9myaseg"},
2387+
json=None,
2388+
)
2389+
2390+
2391+
async def test_save_no_tracks(
2392+
responses: aioresponses,
2393+
authenticated_client: SpotifyClient,
2394+
) -> None:
2395+
"""Test saving no tracks."""
2396+
await authenticated_client.save_tracks([])
2397+
responses.assert_not_called() # type: ignore[no-untyped-call]
2398+
2399+
2400+
async def test_save_too_many_tracks(
2401+
responses: aioresponses,
2402+
authenticated_client: SpotifyClient,
2403+
) -> None:
2404+
"""Test saving too many tracks."""
2405+
with pytest.raises(ValueError, match="Maximum of 50 tracks can be saved at once"):
2406+
await authenticated_client.save_tracks(["abc"] * 51)
2407+
responses.assert_not_called() # type: ignore[no-untyped-call]
2408+
2409+
2410+
async def test_remove_tracks(
2411+
responses: aioresponses,
2412+
authenticated_client: SpotifyClient,
2413+
) -> None:
2414+
"""Test removing tracks."""
2415+
responses.delete(
2416+
f"{SPOTIFY_URL}/v1/me/tracks?ids=0TnOYISbd1XYRBk9myaseg",
2417+
status=200,
2418+
body="",
2419+
)
2420+
await authenticated_client.remove_saved_tracks(["0TnOYISbd1XYRBk9myaseg"])
2421+
responses.assert_called_once_with(
2422+
f"{SPOTIFY_URL}/v1/me/tracks",
2423+
METH_DELETE,
2424+
headers=HEADERS,
2425+
params={"ids": "0TnOYISbd1XYRBk9myaseg"},
2426+
json=None,
2427+
)
2428+
2429+
2430+
async def test_remove_no_tracks(
2431+
responses: aioresponses,
2432+
authenticated_client: SpotifyClient,
2433+
) -> None:
2434+
"""Test removing no tracks."""
2435+
await authenticated_client.remove_saved_tracks([])
2436+
responses.assert_not_called() # type: ignore[no-untyped-call]
2437+
2438+
2439+
async def test_remove_too_many_tracks(
2440+
responses: aioresponses,
2441+
authenticated_client: SpotifyClient,
2442+
) -> None:
2443+
"""Test removing too many tracks."""
2444+
with pytest.raises(ValueError, match="Maximum of 50 tracks can be removed at once"):
2445+
await authenticated_client.remove_saved_tracks(["abc"] * 51)
2446+
responses.assert_not_called() # type: ignore[no-untyped-call]
2447+
2448+
2449+
async def test_check_saved_tracks(
2450+
responses: aioresponses,
2451+
snapshot: SnapshotAssertion,
2452+
authenticated_client: SpotifyClient,
2453+
) -> None:
2454+
"""Test checking saved tracks."""
2455+
responses.get(
2456+
f"{SPOTIFY_URL}/v1/me/tracks/contains?ids=18yVqkdbdRvS24c0Ilj2ci%2C1HGw3J3NxZO1TP1BTtVhpZ",
2457+
status=200,
2458+
body=load_fixture("tracks_saved.json"),
2459+
)
2460+
response = await authenticated_client.are_tracks_saved(
2461+
["18yVqkdbdRvS24c0Ilj2ci", "1HGw3J3NxZO1TP1BTtVhpZ"]
2462+
)
2463+
assert response == snapshot
2464+
responses.assert_called_once_with(
2465+
f"{SPOTIFY_URL}/v1/me/tracks/contains",
2466+
METH_GET,
2467+
headers=HEADERS,
2468+
params={"ids": "18yVqkdbdRvS24c0Ilj2ci,1HGw3J3NxZO1TP1BTtVhpZ"},
2469+
json=None,
2470+
)
2471+
2472+
2473+
async def test_check_no_saved_tracks(
2474+
responses: aioresponses,
2475+
authenticated_client: SpotifyClient,
2476+
) -> None:
2477+
"""Test checking no saved tracks."""
2478+
assert await authenticated_client.are_tracks_saved([]) == {}
2479+
responses.assert_not_called() # type: ignore[no-untyped-call]
2480+
2481+
2482+
async def test_check_too_many_saved_tracks(
2483+
responses: aioresponses,
2484+
authenticated_client: SpotifyClient,
2485+
) -> None:
2486+
"""Test checking too many saved tracks."""
2487+
with pytest.raises(ValueError, match="Maximum of 50 tracks can be checked at once"):
2488+
await authenticated_client.are_tracks_saved(["abc"] * 51)
2489+
responses.assert_not_called() # type: ignore[no-untyped-call]

0 commit comments

Comments
 (0)