Allow the scope to be either a list or comma separated string (#650)

* Allow the scope to be either a list or comma separated string

* Move normalize scope to util file and make it a base method

Also adjust documentation to reflect the new scope parameter supporting a list

* Add change to CHANGELOG.md
This commit is contained in:
Omer Korner 2021-03-04 19:56:39 +02:00 committed by GitHub
parent d001326cc3
commit e07dd64925
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 43 deletions

View File

@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The query parameters of requests are now logged - The query parameters of requests are now logged
- Deprecate specifing `cache_path` or `username` directly to `SpotifyOAuth`, `SpotifyPKCE`, and `SpotifyImplicitGrant` constructors, instead directing users to use the `CacheFileHandler` cache handler - Deprecate specifing `cache_path` or `username` directly to `SpotifyOAuth`, `SpotifyPKCE`, and `SpotifyImplicitGrant` constructors, instead directing users to use the `CacheFileHandler` cache handler
- Removed requirement for examples/app.py to specify port multiple times (only SPOTIPY_REDIRECT_URI needs to contain the port) - Removed requirement for examples/app.py to specify port multiple times (only SPOTIPY_REDIRECT_URI needs to contain the port)
- Add support for a list of scopes rather than just a comma separated string of scopes
### Added ### Added

View File

@ -25,7 +25,7 @@ from six.moves.urllib_parse import parse_qsl, urlparse
from spotipy.cache_handler import CacheFileHandler, CacheHandler from spotipy.cache_handler import CacheFileHandler, CacheHandler
from spotipy.exceptions import SpotifyException from spotipy.exceptions import SpotifyException
from spotipy.util import CLIENT_CREDS_ENV_VARS, get_host_port from spotipy.util import CLIENT_CREDS_ENV_VARS, get_host_port, normalize_scope
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -83,6 +83,9 @@ class SpotifyAuthBase(object):
from requests import api from requests import api
self._session = api self._session = api
def _normalize_scope(self, scope):
return normalize_scope(scope)
@property @property
def client_id(self): def client_id(self):
return self._client_id return self._client_id
@ -254,21 +257,23 @@ class SpotifyOAuth(SpotifyAuthBase):
* client_id: Must be supplied or set as environment variable * client_id: Must be supplied or set as environment variable
* client_secret: Must be supplied or set as environment variable * client_secret: Must be supplied or set as environment variable
* redirect_uri: Must be supplied or set as environment variable * redirect_uri: Must be supplied or set as environment variable
* state: May be supplied, no verification is performed * state: Optional, no verification is performed
* scope: May be supplied, intuitively converted to proper format * scope: Optional, either a list of scopes or comma separated string of scopes.
e.g, "playlist-read-private,playlist-read-collaborative"
* cache_handler: An instance of the `CacheHandler` class to handle * cache_handler: An instance of the `CacheHandler` class to handle
getting and saving cached authorization tokens. getting and saving cached authorization tokens.
May be supplied, will otherwise use `CacheFileHandler`. Optional, will otherwise use `CacheFileHandler`.
(takes precedence over `cache_path` and `username`) (takes precedence over `cache_path` and `username`)
* cache_path: (deprecated) May be supplied, will otherwise be generated * cache_path: (deprecated) Optional, will otherwise be generated
(takes precedence over `username`) (takes precedence over `username`)
* username: (deprecated) May be supplied or set as environment variable * username: (deprecated) Optional or set as environment variable
(will set `cache_path` to `.cache-{username}`) (will set `cache_path` to `.cache-{username}`)
* proxies: Proxy for the requests library to route through * show_dialog: Optional, interpreted as boolean
* show_dialog: Interpreted as boolean * proxies: Optional, proxy for the requests library to route through
* requests_timeout: Tell Requests to stop waiting for a response after a given number * requests_timeout: Optional, tell Requests to stop waiting for a response after
of seconds a given number of seconds
* open_browser: Whether or not the web browser should be opened to authorize a user * open_browser: Optional, whether or not the web browser should be opened to
authorize a user
""" """
super(SpotifyOAuth, self).__init__(requests_session) super(SpotifyOAuth, self).__init__(requests_session)
@ -513,13 +518,6 @@ class SpotifyOAuth(SpotifyAuthBase):
self.cache_handler.save_token_to_cache(token_info) self.cache_handler.save_token_to_cache(token_info)
return token_info if as_dict else token_info["access_token"] return token_info if as_dict else token_info["access_token"]
def _normalize_scope(self, scope):
if scope:
scopes = sorted(scope.split())
return " ".join(scopes)
else:
return None
def refresh_access_token(self, refresh_token): def refresh_access_token(self, refresh_token):
payload = { payload = {
"refresh_token": refresh_token, "refresh_token": refresh_token,
@ -627,21 +625,23 @@ class SpotifyPKCE(SpotifyAuthBase):
* client_id: Must be supplied or set as environment variable * client_id: Must be supplied or set as environment variable
* client_secret: Must be supplied or set as environment variable * client_secret: Must be supplied or set as environment variable
* redirect_uri: Must be supplied or set as environment variable * redirect_uri: Must be supplied or set as environment variable
* state: May be supplied, no verification is performed * state: Optional, no verification is performed
* scope: May be supplied, intuitively converted to proper format * scope: Optional, either a list of scopes or comma separated string of scopes.
e.g, "playlist-read-private,playlist-read-collaborative"
* cache_handler: An instance of the `CacheHandler` class to handle * cache_handler: An instance of the `CacheHandler` class to handle
getting and saving cached authorization tokens. getting and saving cached authorization tokens.
May be supplied, will otherwise use `CacheFileHandler`. Optional, will otherwise use `CacheFileHandler`.
(takes precedence over `cache_path` and `username`) (takes precedence over `cache_path` and `username`)
* cache_path: (deprecated) May be supplied, will otherwise be generated * cache_path: (deprecated) Optional, will otherwise be generated
(takes precedence over `username`) (takes precedence over `username`)
* username: (deprecated) May be supplied or set as environment variable * username: (deprecated) Optional or set as environment variable
(will set `cache_path` to `.cache-{username}`) (will set `cache_path` to `.cache-{username}`)
* show_dialog: Interpreted as boolean * show_dialog: Optional, interpreted as boolean
* proxies: Proxy for the requests library to route through * proxies: Optional, proxy for the requests library to route through
* requests_timeout: Tell Requests to stop waiting for a response after a given number * requests_timeout: Optional, tell Requests to stop waiting for a response after
of seconds a given number of seconds
* open_browser: Whether or not the web browser should be opened to authorize a user * open_browser: Optional, thether or not the web browser should be opened to
authorize a user
""" """
super(SpotifyPKCE, self).__init__(requests_session) super(SpotifyPKCE, self).__init__(requests_session)
@ -683,13 +683,6 @@ class SpotifyPKCE(SpotifyAuthBase):
self.authorization_code = None self.authorization_code = None
self.open_browser = open_browser self.open_browser = open_browser
def _normalize_scope(self, scope):
if scope:
scopes = sorted(scope.split())
return " ".join(scopes)
else:
return None
def _get_code_verifier(self): def _get_code_verifier(self):
""" Spotify PCKE code verifier - See step 1 of the reference guide below """ Spotify PCKE code verifier - See step 1 of the reference guide below
Reference: Reference:
@ -1040,7 +1033,8 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
* client_id: Must be supplied or set as environment variable * client_id: Must be supplied or set as environment variable
* redirect_uri: Must be supplied or set as environment variable * redirect_uri: Must be supplied or set as environment variable
* state: May be supplied, no verification is performed * state: May be supplied, no verification is performed
* scope: May be supplied, intuitively converted to proper format * scope: Optional, either a list of scopes or comma separated string of scopes.
e.g, "playlist-read-private,playlist-read-collaborative"
* cache_handler: An instance of the `CacheHandler` class to handle * cache_handler: An instance of the `CacheHandler` class to handle
getting and saving cached authorization tokens. getting and saving cached authorization tokens.
May be supplied, will otherwise use `CacheFileHandler`. May be supplied, will otherwise use `CacheFileHandler`.
@ -1131,13 +1125,6 @@ class SpotifyImplicitGrant(SpotifyAuthBase):
return token_info["access_token"] return token_info["access_token"]
def _normalize_scope(self, scope):
if scope:
scopes = sorted(scope.split())
return " ".join(scopes)
else:
return None
def get_authorize_url(self, state=None): def get_authorize_url(self, state=None):
""" Gets the URL to use to authorize this app """ """ Gets the URL to use to authorize this app """
payload = { payload = {

View File

@ -117,3 +117,19 @@ def get_host_port(netloc):
port = None port = None
return host, port return host, port
def normalize_scope(scope):
if scope:
if isinstance(scope, str):
scopes = scope.split(',')
elif isinstance(scope, list) or isinstance(scope, tuple):
scopes = scope
else:
raise Exception(
"Unsupported scope value, please either provide a list of scopes, "
"or a string of scopes separated by commas"
)
return " ".join(sorted(scopes))
else:
return None