style: use pipe notation, change minimum version to 3.10

This commit is contained in:
bvandercar-vt 2026-01-23 11:06:52 -07:00
parent c99eea0cb0
commit 94b6305919
7 changed files with 171 additions and 171 deletions

View File

@ -10,7 +10,7 @@ jobs:
SPOTIPY_CLIENT_SECRET: ${{ secrets.SPOTIPY_CLIENT_SECRET }} SPOTIPY_CLIENT_SECRET: ${{ secrets.SPOTIPY_CLIENT_SECRET }}
strategy: strategy:
matrix: matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] python-version: ["3.10", "3.11", "3.12"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] python-version: ["3.10", "3.11", "3.12"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}

View File

@ -31,7 +31,7 @@ setup(
project_urls={ project_urls={
'Source': 'https://github.com/plamere/spotipy', 'Source': 'https://github.com/plamere/spotipy',
}, },
python_requires='>3.8', python_requires='>=3.10',
install_requires=[ install_requires=[
"redis>=3.5.3", # TODO: Move to extras_require in v3 "redis>=3.5.3", # TODO: Move to extras_require in v3
"requests>=2.25.0", "requests>=2.25.0",

View File

@ -12,7 +12,7 @@ import json
import logging import logging
import os import os
from json import JSONEncoder from json import JSONEncoder
from typing import Dict, Optional from typing import Dict
from redis import RedisError from redis import RedisError
@ -53,9 +53,9 @@ class CacheFileHandler(CacheHandler):
def __init__( def __init__(
self, self,
cache_path: Optional[str] = None, cache_path: str | None = None,
username: Optional[str] = None, username: str | None = None,
encoder_cls: Optional[JSONEncoder] = None, encoder_cls: JSONEncoder | None = None,
): ):
""" """
Parameters: Parameters:
@ -113,7 +113,7 @@ class MemoryCacheHandler(CacheHandler):
instance is freed. instance is freed.
""" """
def __init__(self, token_info: Optional[Dict] = None): def __init__(self, token_info: Dict | None = None):
""" """
Parameters: Parameters:
* token_info: The token info to store in memory. Can be None. * token_info: The token info to store in memory. Can be None.
@ -189,7 +189,7 @@ class RedisCacheHandler(CacheHandler):
A cache handler that stores the token info in the Redis. A cache handler that stores the token info in the Redis.
""" """
def __init__(self, redis, key: Optional[str] = None): def __init__(self, redis, key: str | None = None):
""" """
Parameters: Parameters:
* redis: Redis object provided by redis-py library * redis: Redis object provided by redis-py library
@ -222,7 +222,7 @@ class MemcacheCacheHandler(CacheHandler):
"""A Cache handler that stores the token info in Memcache using the pymemcache client """A Cache handler that stores the token info in Memcache using the pymemcache client
""" """
def __init__(self, memcache, key: Optional[str] = None): def __init__(self, memcache, key: str | None = None):
""" """
Parameters: Parameters:
* memcache: memcache client object provided by pymemcache * memcache: memcache client object provided by pymemcache

View File

@ -7,7 +7,7 @@ import logging
import re import re
import warnings import warnings
from collections import defaultdict from collections import defaultdict
from typing import Dict, List, Optional, TypedDict, Union from typing import Dict, List, TypedDict, Union
import requests import requests
@ -138,7 +138,7 @@ class Spotify:
retries: int = max_retries, retries: int = max_retries,
status_retries: int = max_retries, status_retries: int = max_retries,
backoff_factor: float = 0.3, backoff_factor: float = 0.3,
language: Optional[str] = None, language: str | None = None,
): ):
""" """
Creates a Spotify API client. Creates a Spotify API client.
@ -360,7 +360,7 @@ class Spotify:
else: else:
return None return None
def track(self, track_id: str, market: Optional[str] = None): def track(self, track_id: str, market: str | None = None):
""" returns a single track given the track's ID, URI or URL """ returns a single track given the track's ID, URI or URL
Parameters: Parameters:
@ -371,7 +371,7 @@ class Spotify:
trid = self._get_id("track", track_id) trid = self._get_id("track", track_id)
return self._get("tracks/" + trid, market=market) return self._get("tracks/" + trid, market=market)
def tracks(self, tracks: StrListOrTuple, market: Optional[str] = None): def tracks(self, tracks: StrListOrTuple, market: str | None = None):
""" returns a list of tracks given a list of track IDs, URIs, or URLs """ returns a list of tracks given a list of track IDs, URIs, or URLs
Parameters: Parameters:
@ -405,9 +405,9 @@ class Spotify:
def artist_albums( def artist_albums(
self, self,
artist_id: str, artist_id: str,
album_type: Optional[str] = None, album_type: str | None = None,
include_groups: Optional[str] = None, include_groups: str | None = None,
country: Optional[str] = None, country: str | None = None,
limit: int = 20, limit: int = 20,
offset: int = 0, offset: int = 0,
): ):
@ -476,7 +476,7 @@ class Spotify:
trid = self._get_id("artist", artist_id) trid = self._get_id("artist", artist_id)
return self._get("artists/" + trid + "/related-artists") return self._get("artists/" + trid + "/related-artists")
def album(self, album_id: str, market: Optional[str] = None): def album(self, album_id: str, market: str | None = None):
""" returns a single album given the album's ID, URIs or URL """ returns a single album given the album's ID, URIs or URL
Parameters: Parameters:
@ -495,7 +495,7 @@ class Spotify:
album_id: str, album_id: str,
limit: int = 50, limit: int = 50,
offset: int = 0, offset: int = 0,
market: Optional[str] = None, market: str | None = None,
): ):
""" Get Spotify catalog information about an album's tracks """ Get Spotify catalog information about an album's tracks
@ -512,7 +512,7 @@ class Spotify:
"albums/" + trid + "/tracks/", limit=limit, offset=offset, market=market "albums/" + trid + "/tracks/", limit=limit, offset=offset, market=market
) )
def albums(self, albums: StrListOrTuple, market: Optional[str] = None): def albums(self, albums: StrListOrTuple, market: str | None = None):
""" returns a list of albums given the album IDs, URIs, or URLs """ returns a list of albums given the album IDs, URIs, or URLs
Parameters: Parameters:
@ -526,7 +526,7 @@ class Spotify:
else: else:
return self._get("albums/?ids=" + ",".join(tlist)) return self._get("albums/?ids=" + ",".join(tlist))
def show(self, show_id: str, market: Optional[str] = None): def show(self, show_id: str, market: str | None = None):
""" returns a single show given the show's ID, URIs or URL """ returns a single show given the show's ID, URIs or URL
Parameters: Parameters:
@ -541,7 +541,7 @@ class Spotify:
trid = self._get_id("show", show_id) trid = self._get_id("show", show_id)
return self._get("shows/" + trid, market=market) return self._get("shows/" + trid, market=market)
def shows(self, shows: StrListOrTuple, market: Optional[str] = None): def shows(self, shows: StrListOrTuple, market: str | None = None):
""" returns a list of shows given the show IDs, URIs, or URLs """ returns a list of shows given the show IDs, URIs, or URLs
Parameters: Parameters:
@ -561,7 +561,7 @@ class Spotify:
show_id: str, show_id: str,
limit: int = 50, limit: int = 50,
offset: int = 0, offset: int = 0,
market: Optional[str] = None, market: str | None = None,
): ):
""" Get Spotify catalog information about a show's episodes """ Get Spotify catalog information about a show's episodes
@ -581,7 +581,7 @@ class Spotify:
"shows/" + trid + "/episodes/", limit=limit, offset=offset, market=market "shows/" + trid + "/episodes/", limit=limit, offset=offset, market=market
) )
def episode(self, episode_id: str, market: Optional[str] = None): def episode(self, episode_id: str, market: str | None = None):
""" returns a single episode given the episode's ID, URIs or URL """ returns a single episode given the episode's ID, URIs or URL
Parameters: Parameters:
@ -596,7 +596,7 @@ class Spotify:
trid = self._get_id("episode", episode_id) trid = self._get_id("episode", episode_id)
return self._get("episodes/" + trid, market=market) return self._get("episodes/" + trid, market=market)
def episodes(self, episodes: StrListOrTuple, market: Optional[str] = None): def episodes(self, episodes: StrListOrTuple, market: str | None = None):
""" returns a list of episodes given the episode IDs, URIs, or URLs """ returns a list of episodes given the episode IDs, URIs, or URLs
Parameters: Parameters:
@ -617,7 +617,7 @@ class Spotify:
limit: int = 10, limit: int = 10,
offset: int = 0, offset: int = 0,
type: str = "track", type: str = "track",
market: Optional[str] = None, market: str | None = None,
): ):
""" searches for an item """ searches for an item
@ -643,8 +643,8 @@ class Spotify:
limit: int = 10, limit: int = 10,
offset: int = 0, offset: int = 0,
type: str = "track", type: str = "track",
markets: Optional[StrListOrTuple] = None, markets: StrListOrTuple | None = None,
total: Optional[int] = None, total: int | None = None,
): ):
""" (experimental) Searches multiple markets for an item """ (experimental) Searches multiple markets for an item
@ -696,8 +696,8 @@ class Spotify:
def playlist( def playlist(
self, self,
playlist_id: str, playlist_id: str,
fields: Optional[str] = None, fields: str | None = None,
market: Optional[str] = None, market: str | None = None,
additional_types: StrListOrTuple = ("track",), additional_types: StrListOrTuple = ("track",),
): ):
""" Gets playlist by id. """ Gets playlist by id.
@ -721,10 +721,10 @@ class Spotify:
def playlist_tracks( def playlist_tracks(
self, self,
playlist_id: str, playlist_id: str,
fields: Optional[str] = None, fields: str | None = None,
limit: int = 100, limit: int = 100,
offset: int = 0, offset: int = 0,
market: Optional[str] = None, market: str | None = None,
additional_types: StrListOrTuple = ("track",) additional_types: StrListOrTuple = ("track",)
): ):
""" Get full details of the tracks of a playlist. """ Get full details of the tracks of a playlist.
@ -753,10 +753,10 @@ class Spotify:
def playlist_items( def playlist_items(
self, self,
playlist_id: str, playlist_id: str,
fields: Optional[str] = None, fields: str | None = None,
limit: int = 100, limit: int = 100,
offset: int = 0, offset: int = 0,
market: Optional[str] = None, market: str | None = None,
additional_types: StrListOrTuple = ("track", "episode") additional_types: StrListOrTuple = ("track", "episode")
): ):
""" Get full details of the tracks and episodes of a playlist. """ Get full details of the tracks and episodes of a playlist.
@ -807,9 +807,9 @@ class Spotify:
def user_playlist( def user_playlist(
self, self,
user: str, user: str,
playlist_id: Optional[str] = None, playlist_id: str | None = None,
fields: Optional[str] = None, fields: str | None = None,
market: Optional[str] = None, market: str | None = None,
): ):
""" Gets a single playlist of a user """ Gets a single playlist of a user
@ -833,12 +833,12 @@ class Spotify:
def user_playlist_tracks( def user_playlist_tracks(
self, self,
user: Optional[str] = None, user: str | None = None,
playlist_id: Optional[str] = None, playlist_id: str | None = None,
fields: Optional[str] = None, fields: str | None = None,
limit: int = 100, limit: int = 100,
offset: int = 0, offset: int = 0,
market: Optional[str] = None, market: str | None = None,
): ):
""" Get full details of the tracks of a playlist owned by a user. """ Get full details of the tracks of a playlist owned by a user.
@ -908,10 +908,10 @@ class Spotify:
self, self,
user: str, user: str,
playlist_id: str, playlist_id: str,
name: Optional[str] = None, name: str | None = None,
public: Optional[bool] = None, public: bool | None = None,
collaborative: Optional[bool] = None, collaborative: bool | None = None,
description: Optional[str] = None, description: str | None = None,
): ):
""" This function is no longer in use, please use the recommended function in the warning! """ This function is no longer in use, please use the recommended function in the warning!
@ -961,7 +961,7 @@ class Spotify:
user: str, user: str,
playlist_id: str, playlist_id: str,
tracks: StrListOrTuple, tracks: StrListOrTuple,
position: Optional[int] = None, position: int | None = None,
): ):
""" This function is no longer in use, please use the recommended function in the warning! """ This function is no longer in use, please use the recommended function in the warning!
@ -990,7 +990,7 @@ class Spotify:
user: str, user: str,
playlist_id: str, playlist_id: str,
episodes: StrListOrTuple, episodes: StrListOrTuple,
position: Optional[int] = None, position: int | None = None,
): ):
""" This function is no longer in use, please use the recommended function in the warning! """ This function is no longer in use, please use the recommended function in the warning!
@ -1043,7 +1043,7 @@ class Spotify:
range_start: int, range_start: int,
insert_before: int, insert_before: int,
range_length: int = 1, range_length: int = 1,
snapshot_id: Optional[str] = None, snapshot_id: str | None = None,
): ):
""" This function is no longer in use, please use the recommended function in the warning! """ This function is no longer in use, please use the recommended function in the warning!
@ -1076,7 +1076,7 @@ class Spotify:
user: str, user: str,
playlist_id: str, playlist_id: str,
tracks: StrListOrTuple, tracks: StrListOrTuple,
snapshot_id: Optional[str] = None, snapshot_id: str | None = None,
): ):
""" This function is no longer in use, please use the recommended function in the warning! """ This function is no longer in use, please use the recommended function in the warning!
@ -1106,7 +1106,7 @@ class Spotify:
user: str, user: str,
playlist_id: str, playlist_id: str,
tracks: List[TrackOccurances], tracks: List[TrackOccurances],
snapshot_id: Optional[str] = None, snapshot_id: str | None = None,
): ):
""" This function is no longer in use, please use the recommended function in the warning! """ This function is no longer in use, please use the recommended function in the warning!
@ -1191,10 +1191,10 @@ class Spotify:
def playlist_change_details( def playlist_change_details(
self, self,
playlist_id: str, playlist_id: str,
name: Optional[str] = None, name: str | None = None,
public: Optional[bool] = None, public: bool | None = None,
collaborative: Optional[bool] = None, collaborative: bool | None = None,
description: Optional[str] = None, description: str | None = None,
): ):
""" Changes a playlist's name and/or public/private state, """ Changes a playlist's name and/or public/private state,
collaborative state, and/or description collaborative state, and/or description
@ -1232,7 +1232,7 @@ class Spotify:
) )
def playlist_add_items( def playlist_add_items(
self, playlist_id: str, items: StrListOrTuple, position: Optional[int] = None self, playlist_id: str, items: StrListOrTuple, position: int | None = None
): ):
""" Adds tracks/episodes to a playlist """ Adds tracks/episodes to a playlist
@ -1269,7 +1269,7 @@ class Spotify:
range_start: int, range_start: int,
insert_before: int, insert_before: int,
range_length: int = 1, range_length: int = 1,
snapshot_id: Optional[str] = None, snapshot_id: str | None = None,
): ):
""" Reorder tracks in a playlist """ Reorder tracks in a playlist
@ -1295,7 +1295,7 @@ class Spotify:
) )
def playlist_remove_all_occurrences_of_items( def playlist_remove_all_occurrences_of_items(
self, playlist_id: str, items: StrListOrTuple, snapshot_id: Optional[str] = None self, playlist_id: str, items: StrListOrTuple, snapshot_id: str | None = None
): ):
""" Removes all occurrences of the given tracks/episodes from the given playlist """ Removes all occurrences of the given tracks/episodes from the given playlist
@ -1319,7 +1319,7 @@ class Spotify:
self, self,
playlist_id: str, playlist_id: str,
items: List[TrackOccurances], items: List[TrackOccurances],
snapshot_id: Optional[str] = None, snapshot_id: str | None = None,
): ):
""" Removes all occurrences of the given tracks from the given playlist """ Removes all occurrences of the given tracks from the given playlist
@ -1391,7 +1391,7 @@ class Spotify:
def current_user_playing_track( def current_user_playing_track(
self, self,
market: Optional[str] = None, market: str | None = None,
additional_types: StrListOrTuple = ("track",) additional_types: StrListOrTuple = ("track",)
): ):
""" Get information about the current users currently playing track. """ Get information about the current users currently playing track.
@ -1409,7 +1409,7 @@ class Spotify:
) )
def current_user_saved_albums( def current_user_saved_albums(
self, limit: int = 20, offset: int = 0, market: Optional[str] = None self, limit: int = 20, offset: int = 0, market: str | None = None
): ):
""" Gets a list of the albums saved in the current authorized user's """ Gets a list of the albums saved in the current authorized user's
"Your Music" library "Your Music" library
@ -1453,7 +1453,7 @@ class Spotify:
return self._get("me/albums/contains?ids=" + ",".join(alist)) return self._get("me/albums/contains?ids=" + ",".join(alist))
def current_user_saved_tracks( def current_user_saved_tracks(
self, limit: int = 20, offset: int = 0, market: Optional[str] = None self, limit: int = 20, offset: int = 0, market: str | None = None
): ):
""" Gets a list of the tracks saved in the current authorized user's """ Gets a list of the tracks saved in the current authorized user's
"Your Music" library "Your Music" library
@ -1497,7 +1497,7 @@ class Spotify:
return self._get("me/tracks/contains?ids=" + ",".join(tlist)) return self._get("me/tracks/contains?ids=" + ",".join(tlist))
def current_user_saved_episodes( def current_user_saved_episodes(
self, limit: int = 20, offset: int = 0, market: Optional[str] = None self, limit: int = 20, offset: int = 0, market: str | None = None
): ):
""" Gets a list of the episodes saved in the current authorized user's """ Gets a list of the episodes saved in the current authorized user's
"Your Music" library "Your Music" library
@ -1541,7 +1541,7 @@ class Spotify:
return self._get("me/episodes/contains?ids=" + ",".join(elist)) return self._get("me/episodes/contains?ids=" + ",".join(elist))
def current_user_saved_shows( def current_user_saved_shows(
self, limit: int = 20, offset: int = 0, market: Optional[str] = None self, limit: int = 20, offset: int = 0, market: str | None = None
): ):
""" Gets a list of the shows saved in the current authorized user's """ Gets a list of the shows saved in the current authorized user's
"Your Music" library "Your Music" library
@ -1584,7 +1584,7 @@ class Spotify:
return self._get("me/shows/contains?ids=" + ",".join(slist)) return self._get("me/shows/contains?ids=" + ",".join(slist))
def current_user_followed_artists( def current_user_followed_artists(
self, limit: int = 20, after: Optional[str] = None self, limit: int = 20, after: str | None = None
): ):
""" Gets a list of the artists followed by the current authorized user """ Gets a list of the artists followed by the current authorized user
@ -1655,7 +1655,7 @@ class Spotify:
) )
def current_user_recently_played( def current_user_recently_played(
self, limit: int = 50, after: Optional[int] = None, before: Optional[int] = None self, limit: int = 50, after: int | None = None, before: int | None = None
): ):
""" Get the current user's recently played tracks """ Get the current user's recently played tracks
@ -1705,9 +1705,9 @@ class Spotify:
def featured_playlists( def featured_playlists(
self, self,
locale: Optional[str] = None, locale: str | None = None,
country: Optional[str] = None, country: str | None = None,
timestamp: Optional[str] = None, timestamp: str | None = None,
limit: int = 20, limit: int = 20,
offset: int = 0, offset: int = 0,
): ):
@ -1750,7 +1750,7 @@ class Spotify:
) )
def new_releases( def new_releases(
self, country: Optional[str] = None, limit: int = 20, offset: int = 0 self, country: str | None = None, limit: int = 20, offset: int = 0
): ):
""" Get a list of new album releases featured in Spotify """ Get a list of new album releases featured in Spotify
@ -1771,8 +1771,8 @@ class Spotify:
def category( def category(
self, self,
category_id: str, category_id: str,
country: Optional[str] = None, country: str | None = None,
locale: Optional[str] = None, locale: str | None = None,
): ):
""" Get info about a category """ Get info about a category
@ -1792,8 +1792,8 @@ class Spotify:
def categories( def categories(
self, self,
country: Optional[str] = None, country: str | None = None,
locale: Optional[str] = None, locale: str | None = None,
limit: int = 20, limit: int = 20,
offset: int = 0, offset: int = 0,
): ):
@ -1822,8 +1822,8 @@ class Spotify:
def category_playlists( def category_playlists(
self, self,
category_id: Optional[str] = None, category_id: str | None = None,
country: Optional[str] = None, country: str | None = None,
limit: int = 20, limit: int = 20,
offset: int = 0, offset: int = 0,
): ):
@ -1858,11 +1858,11 @@ class Spotify:
def recommendations( def recommendations(
self, self,
seed_artists: Optional[StrListOrTuple] = None, seed_artists: StrListOrTuple | None = None,
seed_genres: Optional[StrListOrTuple] = None, seed_genres: StrListOrTuple | None = None,
seed_tracks: Optional[StrListOrTuple] = None, seed_tracks: StrListOrTuple | None = None,
limit: int = 20, limit: int = 20,
country: Optional[str] = None, country: str | None = None,
**kwargs **kwargs
): ):
""" Get a list of recommended tracks for one to five seeds. """ Get a list of recommended tracks for one to five seeds.
@ -1995,7 +1995,7 @@ class Spotify:
return self._get("me/player/devices") return self._get("me/player/devices")
def current_playback( def current_playback(
self, market: Optional[str] = None, additional_types: Optional[str] = None self, market: str = None, additional_types: str | None = None
): ):
""" Get information about user's current playback. """ Get information about user's current playback.
@ -2006,7 +2006,7 @@ class Spotify:
return self._get("me/player", market=market, additional_types=additional_types) return self._get("me/player", market=market, additional_types=additional_types)
def currently_playing( def currently_playing(
self, market: Optional[str] = None, additional_types: Optional[str] = None self, market: str | None = None, additional_types: str | None = None
): ):
""" Get user's currently playing track. """ Get user's currently playing track.
@ -2037,11 +2037,11 @@ class Spotify:
def start_playback( def start_playback(
self, self,
device_id: Optional[str] = None, device_id: str | None = None,
context_uri: Optional[str] = None, context_uri: str | None = None,
uris: Optional[List[str]] = None, uris: List[str] | None = None,
offset: Optional[PlaybackOffset] = None, offset: PlaybackOffset | None = None,
position_ms: Optional[Union[int, float]] = None, position_ms: Union[int, float] | None = None,
): ):
""" Start or resume user's playback. """ Start or resume user's playback.
@ -2083,7 +2083,7 @@ class Spotify:
self._append_device_id("me/player/play", device_id), payload=data self._append_device_id("me/player/play", device_id), payload=data
) )
def pause_playback(self, device_id: Optional[str] = None): def pause_playback(self, device_id: str | None = None):
""" Pause user's playback. """ Pause user's playback.
Parameters: Parameters:
@ -2091,7 +2091,7 @@ class Spotify:
""" """
return self._put(self._append_device_id("me/player/pause", device_id)) return self._put(self._append_device_id("me/player/pause", device_id))
def next_track(self, device_id: Optional[str] = None): def next_track(self, device_id: str | None = None):
""" Skip user's playback to next track. """ Skip user's playback to next track.
Parameters: Parameters:
@ -2099,7 +2099,7 @@ class Spotify:
""" """
return self._post(self._append_device_id("me/player/next", device_id)) return self._post(self._append_device_id("me/player/next", device_id))
def previous_track(self, device_id: Optional[str] = None): def previous_track(self, device_id: str | None = None):
""" Skip user's playback to previous track. """ Skip user's playback to previous track.
Parameters: Parameters:
@ -2110,7 +2110,7 @@ class Spotify:
) )
def seek_track( def seek_track(
self, position_ms: Union[int, float], device_id: Optional[str] = None self, position_ms: Union[int, float], device_id: str | None = None
): ):
""" Seek to position in current track. """ Seek to position in current track.
@ -2127,7 +2127,7 @@ class Spotify:
) )
) )
def repeat(self, state: str, device_id: Optional[str] = None): def repeat(self, state: str, device_id: str | None = None):
""" Set repeat mode for playback. """ Set repeat mode for playback.
Parameters: Parameters:
@ -2143,7 +2143,7 @@ class Spotify:
) )
) )
def volume(self, volume_percent: int, device_id: Optional[str] = None): def volume(self, volume_percent: int, device_id: str | None = None):
""" Set playback volume. """ Set playback volume.
Parameters: Parameters:
@ -2163,7 +2163,7 @@ class Spotify:
) )
) )
def shuffle(self, state: bool, device_id: Optional[str] = None): def shuffle(self, state: bool, device_id: str | None = None):
""" Toggle playback shuffling. """ Toggle playback shuffling.
Parameters: Parameters:
@ -2184,7 +2184,7 @@ class Spotify:
""" Gets the current user's queue """ """ Gets the current user's queue """
return self._get("me/player/queue") return self._get("me/player/queue")
def add_to_queue(self, uri: str, device_id: Optional[str] = None): def add_to_queue(self, uri: str, device_id: str | None = None):
""" Adds a song to the end of a user's queue """ Adds a song to the end of a user's queue
If device A is currently playing music, and you try to add to the queue If device A is currently playing music, and you try to add to the queue
@ -2215,7 +2215,7 @@ class Spotify:
""" """
return self._get("markets") return self._get("markets")
def _append_device_id(self, path: str, device_id: Optional[str]) -> str: def _append_device_id(self, path: str, device_id: str | None) -> str:
""" Append device ID to API path. """ Append device ID to API path.
Parameters: Parameters:
@ -2271,7 +2271,7 @@ class Spotify:
offset: int, offset: int,
type: str, type: str,
markets: StrListOrTuple, markets: StrListOrTuple,
total: Optional[int], total: int | None,
): ):
if total and limit > total: if total and limit > total:
limit = total limit = total
@ -2306,7 +2306,7 @@ class Spotify:
return results return results
def get_audiobook(self, id: str, market: Optional[str] = None): def get_audiobook(self, id: str, market: str | None = None):
""" Get Spotify catalog information for a single audiobook identified by its unique """ Get Spotify catalog information for a single audiobook identified by its unique
Spotify ID. Spotify ID.
@ -2322,7 +2322,7 @@ class Spotify:
return self._get(endpoint) return self._get(endpoint)
def get_audiobooks(self, ids: StrListOrTuple, market: Optional[str] = None): def get_audiobooks(self, ids: StrListOrTuple, market: str | None = None):
""" Get Spotify catalog information for multiple audiobooks based on their Spotify IDs. """ Get Spotify catalog information for multiple audiobooks based on their Spotify IDs.
Parameters: Parameters:
@ -2338,7 +2338,7 @@ class Spotify:
return self._get(endpoint) return self._get(endpoint)
def get_audiobook_chapters( def get_audiobook_chapters(
self, id: str, market: Optional[str] = None, limit: int = 20, offset: int = 0 self, id: str, market: str | None = None, limit: int = 20, offset: int = 0
): ):
""" Get Spotify catalog information about an audiobooks chapters. """ Get Spotify catalog information about an audiobooks chapters.

View File

@ -16,7 +16,7 @@ import urllib.parse as urllibparse
import warnings import warnings
import webbrowser import webbrowser
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Any, Dict, Optional, Union from typing import Any, Dict, Union
from urllib.parse import parse_qsl, urlparse from urllib.parse import parse_qsl, urlparse
import requests import requests
@ -38,7 +38,7 @@ def _make_authorization_headers(client_id: str, client_secret: str):
return {"Authorization": f"Basic {auth_header.decode('ascii')}"} return {"Authorization": f"Basic {auth_header.decode('ascii')}"}
def _ensure_value(value: Optional[str], env_key: str) -> str: def _ensure_value(value: str | None, env_key: str) -> str:
env_val = CLIENT_CREDS_ENV_VARS[env_key] env_val = CLIENT_CREDS_ENV_VARS[env_key]
_val = value or os.getenv(env_val) _val = value or os.getenv(env_val)
if _val is None: if _val is None:
@ -48,7 +48,7 @@ def _ensure_value(value: Optional[str], env_key: str) -> str:
class SpotifyAuthBase: class SpotifyAuthBase:
def __init__(self, requests_session: Optional[Union[requests.Session, bool]] = None): def __init__(self, requests_session: Union[requests.Session, bool] | None = None):
if isinstance(requests_session, requests.Session): if isinstance(requests_session, requests.Session):
self._session = requests_session self._session = requests_session
else: else:
@ -58,7 +58,7 @@ class SpotifyAuthBase:
from requests import api from requests import api
self._session = api self._session = api
def _normalize_scope(self, scope: Optional[ScopeArgType]): def _normalize_scope(self, scope: ScopeArgType | None):
return normalize_scope(scope) return normalize_scope(scope)
@property @property
@ -66,7 +66,7 @@ class SpotifyAuthBase:
return self._client_id return self._client_id
@client_id.setter @client_id.setter
def client_id(self, val: Optional[str]): def client_id(self, val: str | None):
self._client_id = _ensure_value(val, "client_id") self._client_id = _ensure_value(val, "client_id")
@property @property
@ -74,7 +74,7 @@ class SpotifyAuthBase:
return self._client_secret return self._client_secret
@client_secret.setter @client_secret.setter
def client_secret(self, val: Optional[str]): def client_secret(self, val: str | None):
self._client_secret = _ensure_value(val, "client_secret") self._client_secret = _ensure_value(val, "client_secret")
@property @property
@ -82,7 +82,7 @@ class SpotifyAuthBase:
return self._redirect_uri return self._redirect_uri
@redirect_uri.setter @redirect_uri.setter
def redirect_uri(self, val: Optional[str]): def redirect_uri(self, val: str | None):
self._redirect_uri = _ensure_value(val, "redirect_uri") self._redirect_uri = _ensure_value(val, "redirect_uri")
@staticmethod @staticmethod
@ -99,7 +99,7 @@ class SpotifyAuthBase:
@staticmethod @staticmethod
def _is_scope_subset( def _is_scope_subset(
needle_scope: Optional[str], haystack_scope: Optional[str] needle_scope: str | None, haystack_scope: str | None
) -> bool: ) -> bool:
needle_scope = set(needle_scope.split()) if needle_scope else set() needle_scope = set(needle_scope.split()) if needle_scope else set()
haystack_scope = set(haystack_scope.split()) if haystack_scope else set() haystack_scope = set(haystack_scope.split()) if haystack_scope else set()
@ -136,12 +136,12 @@ class SpotifyClientCredentials(SpotifyAuthBase):
def __init__( def __init__(
self, self,
client_id: Optional[str] = None, client_id: str | None = None,
client_secret: Optional[str] = None, client_secret: str | None = None,
proxies: Optional[Any] = None, proxies: Any | None = None,
requests_session: Union[requests.Session, bool] = True, requests_session: Union[requests.Session, bool] = True,
requests_timeout: Optional[int] = None, requests_timeout: int | None = None,
cache_handler: Optional[CacheHandler] = None, cache_handler: CacheHandler | None = None,
): ):
""" """
Creates a Client Credentials Flow Manager. Creates a Client Credentials Flow Manager.
@ -259,19 +259,19 @@ class SpotifyOAuth(SpotifyAuthBase):
def __init__( def __init__(
self, self,
client_id: Optional[str] = None, client_id: str | None = None,
client_secret: Optional[str] = None, client_secret: str | None = None,
redirect_uri: Optional[str] = None, redirect_uri: str | None = None,
state: Optional[Any] = None, state: Any | None = None,
scope: Optional[ScopeArgType] = None, scope: ScopeArgType | None = None,
cache_path: Optional[str] = None, cache_path: str | None = None,
username: Optional[str] = None, username: str | None = None,
proxies: Optional[Any] = None, proxies: Any | None = None,
show_dialog: bool = False, show_dialog: bool = False,
requests_session: Union[requests.Session, bool] = True, requests_session: Union[requests.Session, bool] = True,
requests_timeout: Optional[int] = None, requests_timeout: int | None = None,
open_browser: bool = True, open_browser: bool = True,
cache_handler: Optional[CacheHandler] = None, cache_handler: CacheHandler | None = None,
): ):
""" """
Creates a SpotifyOAuth object Creates a SpotifyOAuth object
@ -339,7 +339,7 @@ class SpotifyOAuth(SpotifyAuthBase):
self.show_dialog = show_dialog self.show_dialog = show_dialog
self.open_browser = open_browser self.open_browser = open_browser
def validate_token(self, token_info: Optional[Dict]): def validate_token(self, token_info: Dict | None):
if token_info is None: if token_info is None:
return None return None
@ -356,7 +356,7 @@ class SpotifyOAuth(SpotifyAuthBase):
return token_info return token_info
def get_authorize_url(self, state: Optional[Any] = None) -> str: def get_authorize_url(self, state: Any | None = None) -> str:
""" Gets the URL to use to authorize this app """ Gets the URL to use to authorize this app
""" """
payload = { payload = {
@ -439,7 +439,7 @@ class SpotifyOAuth(SpotifyAuthBase):
else: else:
raise SpotifyOauthError("Server listening on localhost has not been accessed") raise SpotifyOauthError("Server listening on localhost has not been accessed")
def get_auth_response(self, open_browser: Optional[bool] = None): def get_auth_response(self, open_browser: bool | None = None):
logger.info('User authentication requires interaction with your ' logger.info('User authentication requires interaction with your '
'web browser. Once you enter your credentials and ' 'web browser. Once you enter your credentials and '
'give authorization, you will be redirected to ' 'give authorization, you will be redirected to '
@ -480,13 +480,13 @@ class SpotifyOAuth(SpotifyAuthBase):
return self._get_auth_response_interactive(open_browser=open_browser) return self._get_auth_response_interactive(open_browser=open_browser)
def get_authorization_code(self, response: Optional[Any] = None): def get_authorization_code(self, response: Any | None = None):
if response: if response:
return self.parse_response_code(response) return self.parse_response_code(response)
return self.get_auth_response() return self.get_auth_response()
def get_access_token( def get_access_token(
self, code: Optional[str] = None, as_dict: bool = True, check_cache: bool = True self, code: str | None = None, as_dict: bool = True, check_cache: bool = True
): ):
""" Gets the access token for the app given the code """ Gets the access token for the app given the code
@ -627,17 +627,17 @@ class SpotifyPKCE(SpotifyAuthBase):
def __init__( def __init__(
self, self,
client_id: Optional[str] = None, client_id: str | None = None,
redirect_uri: Optional[str] = None, redirect_uri: str | None = None,
state: Optional[Any] = None, state: Any | None = None,
scope: Optional[ScopeArgType] = None, scope: ScopeArgType | None = None,
cache_path: Optional[str] = None, cache_path: str | None = None,
username: Optional[str] = None, username: str | None = None,
proxies: Optional[Any] = None, proxies: Any | None = None,
requests_timeout: Optional[int] = None, requests_timeout: int | None = None,
requests_session: Union[requests.Session, bool] = True, requests_session: Union[requests.Session, bool] = True,
open_browser: bool = True, open_browser: bool = True,
cache_handler: Optional[CacheHandler] = None, cache_handler: CacheHandler | None = None,
): ):
""" """
Creates Auth Manager with the PKCE Auth flow. Creates Auth Manager with the PKCE Auth flow.
@ -728,7 +728,7 @@ class SpotifyPKCE(SpotifyAuthBase):
code_challenge = base64.urlsafe_b64encode(code_challenge_digest).decode('utf-8') code_challenge = base64.urlsafe_b64encode(code_challenge_digest).decode('utf-8')
return code_challenge.replace('=', '') return code_challenge.replace('=', '')
def get_authorize_url(self, state: Optional[Any] = None) -> str: def get_authorize_url(self, state: Any | None = None) -> str:
""" Gets the URL to use to authorize this app """ """ Gets the URL to use to authorize this app """
if not self.code_challenge: if not self.code_challenge:
self.get_pkce_handshake_parameters() self.get_pkce_handshake_parameters()
@ -748,7 +748,7 @@ class SpotifyPKCE(SpotifyAuthBase):
urlparams = urllibparse.urlencode(payload) urlparams = urllibparse.urlencode(payload)
return f"{self.OAUTH_AUTHORIZE_URL}?{urlparams}" return f"{self.OAUTH_AUTHORIZE_URL}?{urlparams}"
def _open_auth_url(self, state: Optional[Any] = None): def _open_auth_url(self, state: Any | None = None):
auth_url = self.get_authorize_url(state) auth_url = self.get_authorize_url(state)
try: try:
webbrowser.open(auth_url) webbrowser.open(auth_url)
@ -756,7 +756,7 @@ class SpotifyPKCE(SpotifyAuthBase):
except webbrowser.Error: except webbrowser.Error:
logger.error(f"Please navigate here: {auth_url}") logger.error(f"Please navigate here: {auth_url}")
def _get_auth_response(self, open_browser: Optional[bool] = None): def _get_auth_response(self, open_browser: bool | None = None):
logger.info('User authentication requires interaction with your ' logger.info('User authentication requires interaction with your '
'web browser. Once you enter your credentials and ' 'web browser. Once you enter your credentials and '
'give authorization, you will be redirected to ' 'give authorization, you will be redirected to '
@ -825,7 +825,7 @@ class SpotifyPKCE(SpotifyAuthBase):
raise SpotifyStateError(self.state, state) raise SpotifyStateError(self.state, state)
return code return code
def get_authorization_code(self, response: Optional[Any] = None): def get_authorization_code(self, response: Any | None = None):
if response: if response:
return self.parse_response_code(response) return self.parse_response_code(response)
return self._get_auth_response() return self._get_auth_response()
@ -859,7 +859,7 @@ class SpotifyPKCE(SpotifyAuthBase):
self.code_verifier = self._get_code_verifier() self.code_verifier = self._get_code_verifier()
self.code_challenge = self._get_code_challenge() self.code_challenge = self._get_code_challenge()
def get_access_token(self, code: Optional[Any] = None, check_cache: bool = True): def get_access_token(self, code: Any | None = None, check_cache: bool = True):
""" Gets the access token for the app """ Gets the access token for the app
If the code is not given and no cached token is used, an If the code is not given and no cached token is used, an
@ -1018,14 +1018,14 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
def __init__( def __init__(
self, self,
client_id: Optional[str] = None, client_id: str | None = None,
redirect_uri: Optional[str] = None, redirect_uri: str | None = None,
state: Optional[Any] = None, state: Any | None = None,
scope: Optional[ScopeArgType] = None, scope: ScopeArgType | None = None,
cache_path: Optional[str] = None, cache_path: str | None = None,
username: Optional[str] = None, username: str | None = None,
show_dialog: bool = False, show_dialog: bool = False,
cache_handler: Optional[CacheHandler] = None, cache_handler: CacheHandler | None = None,
): ):
""" Creates Auth Manager using the Implicit Grant flow """ Creates Auth Manager using the Implicit Grant flow
@ -1087,7 +1087,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
self.show_dialog = show_dialog self.show_dialog = show_dialog
self._session = None # As to not break inherited __del__ self._session = None # As to not break inherited __del__
def validate_token(self, token_info: Optional[Dict]): def validate_token(self, token_info: Dict | None):
if token_info is None: if token_info is None:
return None return None
@ -1104,8 +1104,8 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
def get_access_token( def get_access_token(
self, self,
state: Optional[Any] = None, state: Any | None = None,
response: Optional[Any] = None, response: Any | None = None,
check_cache: bool = True, check_cache: bool = True,
): ):
""" Gets Auth Token from cache (preferred) or user interaction """ Gets Auth Token from cache (preferred) or user interaction
@ -1130,7 +1130,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
return token_info["access_token"] return token_info["access_token"]
def get_authorize_url(self, state: Optional[Any] = None) -> str: def get_authorize_url(self, state: Any | None = None) -> str:
""" Gets the URL to use to authorize this app """ """ Gets the URL to use to authorize this app """
payload = { payload = {
"client_id": self.client_id, "client_id": self.client_id,
@ -1150,7 +1150,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
return f"{self.OAUTH_AUTHORIZE_URL}?{urlparams}" return f"{self.OAUTH_AUTHORIZE_URL}?{urlparams}"
def parse_response_token(self, url, state: Optional[Any] = None): def parse_response_token(self, url, state: Any | None = None):
""" Parse the response code in the given response url """ """ Parse the response code in the given response url """
remote_state, token, t_type, exp_in = self.parse_auth_response_url(url) remote_state, token, t_type, exp_in = self.parse_auth_response_url(url)
if state is None: if state is None:
@ -1175,7 +1175,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
return tuple(form.get(param) for param in ["state", "access_token", return tuple(form.get(param) for param in ["state", "access_token",
"token_type", "expires_in"]) "token_type", "expires_in"])
def _open_auth_url(self, state: Optional[Any] = None): def _open_auth_url(self, state: Any | None = None):
auth_url = self.get_authorize_url(state) auth_url = self.get_authorize_url(state)
try: try:
webbrowser.open(auth_url) webbrowser.open(auth_url)
@ -1183,7 +1183,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
except webbrowser.Error: except webbrowser.Error:
logger.error(f"Please navigate here: {auth_url}") logger.error(f"Please navigate here: {auth_url}")
def get_auth_response(self, state: Optional[Any] = None): def get_auth_response(self, state: Any | None = None):
""" Gets a new auth **token** with user interaction """ """ Gets a new auth **token** with user interaction """
logger.info('User authentication requires interaction with your ' logger.info('User authentication requires interaction with your '
'web browser. Once you enter your credentials and ' 'web browser. Once you enter your credentials and '

View File

@ -8,7 +8,7 @@ import logging
import os import os
import warnings import warnings
from types import TracebackType from types import TracebackType
from typing import List, Optional, Tuple, Union from typing import List, Tuple, Union
import requests import requests
import urllib3 import urllib3
@ -31,15 +31,15 @@ StrListOrTuple = Union[List[str], Tuple[str, ...]]
def prompt_for_user_token( def prompt_for_user_token(
username: Optional[str] = None, username: str | None = None,
scope: Optional[str] = None, scope: str | None = None,
client_id: Optional[str] = None, client_id: str | None = None,
client_secret: Optional[str] = None, client_secret: str | None = None,
redirect_uri: Optional[str] = None, redirect_uri: str | None = None,
cache_path: Optional[str] = None, cache_path: str | None = None,
oauth_manager: Optional[spotipy.SpotifyOAuth] = None, oauth_manager: spotipy.SpotifyOAuth | None = None,
show_dialog: bool = False, show_dialog: bool = False,
) -> Union[str, None]: ) -> str | None:
""" Prompt the user to login if necessary and returns a user token """ Prompt the user to login if necessary and returns a user token
suitable for use with the spotipy.Spotify constructor. suitable for use with the spotipy.Spotify constructor.
@ -139,7 +139,7 @@ def get_host_port(netloc: str):
ScopeArgType = Union[str, StrListOrTuple] ScopeArgType = Union[str, StrListOrTuple]
def normalize_scope(scope: Optional[ScopeArgType]) -> Union[str, None]: def normalize_scope(scope: ScopeArgType | None) -> str | None:
"""Normalize the scope to verify that it is a list or tuple. A string """Normalize the scope to verify that it is a list or tuple. A string
input will split the string by commas to create a list of scopes. input will split the string by commas to create a list of scopes.
A list or tuple input is used directly. A list or tuple input is used directly.
@ -170,12 +170,12 @@ class Retry(urllib3.Retry):
def increment( def increment(
self, self,
method: Optional[str] = None, method: str | None = None,
url: Optional[str] = None, url: str | None = None,
response: Optional[urllib3.BaseHTTPResponse] = None, response: urllib3.BaseHTTPResponse | None = None,
error: Optional[Exception] = None, error: Exception | None = None,
_pool: Optional[urllib3.connectionpool.ConnectionPool] = None, _pool: urllib3.connectionpool.ConnectionPool | None = None,
_stacktrace: Optional[TracebackType] = None, _stacktrace: TracebackType | None = None,
) -> urllib3.Retry: ) -> urllib3.Retry:
if response: if response:
retry_header = response.headers.get("Retry-After") retry_header = response.headers.get("Retry-After")