mirror of
https://github.com/spotipy-dev/spotipy.git
synced 2026-06-19 01:03:53 +00:00
Changed cache_handler.py to utilize Python's Context Management Protocol (#991)
This commit is contained in:
parent
d319c6e09f
commit
1dbbbf65ec
1
.github/workflows/integration_tests.yml
vendored
1
.github/workflows/integration_tests.yml
vendored
@ -4,7 +4,6 @@ on: [push, pull_request]
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
SPOTIPY_CLIENT_ID: ${{ secrets.SPOTIPY_CLIENT_ID }}
|
SPOTIPY_CLIENT_ID: ${{ secrets.SPOTIPY_CLIENT_ID }}
|
||||||
|
|||||||
1
.github/workflows/unit_tests.yml
vendored
1
.github/workflows/unit_tests.yml
vendored
@ -4,7 +4,6 @@ on: [push, pull_request]
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
79
CHANGELOG.md
79
CHANGELOG.md
@ -6,22 +6,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
Add your changes below.
|
Add your changes below.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added examples for audiobooks, shows and episodes methods to examples directory
|
- Added examples for audiobooks, shows and episodes methods to examples directory
|
||||||
- Use newer string formatters (https://pyformat.info)
|
- Use newer string formatters (https://pyformat.info)
|
||||||
- Marked `recommendation_genre_seeds` as deprecated
|
- Marked `recommendation_genre_seeds` as deprecated
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed scripts in examples directory that didn't run correctly
|
- Fixed scripts in examples directory that didn't run correctly
|
||||||
- Updated documentation for `Client.current_user_top_artists` to indicate maximum number of artists limit
|
- Updated documentation for `Client.current_user_top_artists` to indicate maximum number of artists limit
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated get_cached_token and save_token_to_cache methods to utilize Python's Context Management Protocol
|
||||||
|
- Added except clause to get_cached_token method to handle json decode errors
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
## [2.25.0] - 2025-03-01
|
## [2.25.0] - 2025-03-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added unit tests for queue functions
|
- Added unit tests for queue functions
|
||||||
- Added detailed function docstrings to 'util.py', including descriptions and special sections that lists arguments, returns, and raises.
|
- Added detailed function docstrings to 'util.py', including descriptions and special sections that lists arguments, returns, and raises.
|
||||||
- Updated order of instructions for Python and pip package manager installation in TUTORIAL.md
|
- Updated order of instructions for Python and pip package manager installation in TUTORIAL.md
|
||||||
@ -44,53 +53,66 @@ Add your changes below.
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Split test and lint workflows
|
- Split test and lint workflows
|
||||||
|
- Updated get_cached_token and save_token_to_cache methods to utilize Python's Context Management Protocol
|
||||||
|
- Added except clause to get_cached_token method to handle json decode errors
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Audiobook integration tests
|
- Audiobook integration tests
|
||||||
- Edited docstrings for certain functions in client.py for functions that are no longer in use and have been replaced.
|
- Edited docstrings for certain functions in client.py for functions that are no longer in use and have been replaced.
|
||||||
- `current_user_unfollow_playlist()` now supports playlist IDs, URLs, and URIs rather than previously where it only supported playlist IDs.
|
- `current_user_unfollow_playlist()` now supports playlist IDs, URLs, and URIs rather than previously where it only supported playlist IDs.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- `mock` no longer listed as a test dependency. Only built-in `unittest.mock` is actually used.
|
- `mock` no longer listed as a test dependency. Only built-in `unittest.mock` is actually used.
|
||||||
|
|
||||||
## [2.24.0] - 2024-05-30
|
## [2.24.0] - 2024-05-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `MemcacheCacheHandler`, a cache handler that stores the token info using pymemcache.
|
- Added `MemcacheCacheHandler`, a cache handler that stores the token info using pymemcache.
|
||||||
- Added support for audiobook endpoints: `get_audiobook`, `get_audiobooks`, and `get_audiobook_chapters`.
|
- Added support for audiobook endpoints: `get_audiobook`, `get_audiobooks`, and `get_audiobook_chapters`.
|
||||||
- Added integration tests for audiobook endpoints.
|
- Added integration tests for audiobook endpoints.
|
||||||
- Added `update` field to `current_user_follow_playlist`.
|
- Added `update` field to `current_user_follow_playlist`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Updated get_cached_token and save_token_to_cache methods to utilize Python's Context Management Protocol
|
||||||
|
- Added except clause to get_cached_token method to handle json decode errors
|
||||||
|
|
||||||
- Fixed error obfuscation when Spotify class is being inherited and an error is raised in the Child's `__init__`
|
- Fixed error obfuscation when Spotify class is being inherited and an error is raised in the Child's `__init__`
|
||||||
- Replaced `artist_albums(album_type=...)` with `artist_albums(include_groups=...)` due to an API change.
|
- Replaced `artist_albums(album_type=...)` with `artist_albums(include_groups=...)` due to an API change.
|
||||||
- Updated `_regex_spotify_url` to ignore `/intl-<countrycode>` in Spotify links
|
- Updated `_regex_spotify_url` to ignore `/intl-<countrycode>` in Spotify links
|
||||||
- Improved README, docs and examples
|
- Improved README, docs and examples
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Readthedocs build
|
- Readthedocs build
|
||||||
- Split `test_current_user_save_and_usave_tracks` unit test
|
- Split `test_current_user_save_and_usave_tracks` unit test
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Drop support for EOL Python 3.7
|
- Drop support for EOL Python 3.7
|
||||||
|
|
||||||
## [2.23.0] - 2023-04-07
|
## [2.23.0] - 2023-04-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added optional `encoder_cls` argument to `CacheFileHandler`, which overwrite default encoder for token before writing to disk
|
- Added optional `encoder_cls` argument to `CacheFileHandler`, which overwrite default encoder for token before writing to disk
|
||||||
- Integration tests for searching multiple types in multiple markets (non-user endpoints)
|
- Integration tests for searching multiple types in multiple markets (non-user endpoints)
|
||||||
- Publish to PyPI action
|
- Publish to PyPI action
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed the regex for matching playlist URIs with the format spotify:user:USERNAME:playlist:PLAYLISTID.
|
- Fixed the regex for matching playlist URIs with the format spotify:user:USERNAME:playlist:PLAYLISTID.
|
||||||
- `search_markets` now factors the counts of all types in the `total` rather than just the first type ([#534](https://github.com/spotipy-dev/spotipy/issues/534))
|
- `search_markets` now factors the counts of all types in the `total` rather than just the first type ([#534](https://github.com/spotipy-dev/spotipy/issues/534))
|
||||||
|
|
||||||
## [2.22.1] - 2023-01-23
|
## [2.22.1] - 2023-01-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add alternative module installation instruction to README
|
- Add alternative module installation instruction to README
|
||||||
- Added Comment to README - Getting Started for user to add URI to app in Spotify Developer Dashboard.
|
- Added Comment to README - Getting Started for user to add URI to app in Spotify Developer Dashboard.
|
||||||
- Added playlist_add_tracks.py to example folder
|
- Added playlist_add_tracks.py to example folder
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@ -170,11 +192,11 @@ Add your changes below.
|
|||||||
- Enabled using both short and long IDs for playlist_change_details
|
- Enabled using both short and long IDs for playlist_change_details
|
||||||
- Added a cache handler to `SpotifyClientCredentials`
|
- Added a cache handler to `SpotifyClientCredentials`
|
||||||
- Added the following endpoints
|
- Added the following endpoints
|
||||||
- `Spotify.current_user_saved_episodes`
|
- `Spotify.current_user_saved_episodes`
|
||||||
- `Spotify.current_user_saved_episodes_add`
|
- `Spotify.current_user_saved_episodes_add`
|
||||||
- `Spotify.current_user_saved_episodes_delete`
|
- `Spotify.current_user_saved_episodes_delete`
|
||||||
- `Spotify.current_user_saved_episodes_contains`
|
- `Spotify.current_user_saved_episodes_contains`
|
||||||
- `Spotify.available_markets`
|
- `Spotify.available_markets`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -240,7 +262,7 @@ Add your changes below.
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- `SpotifyPKCE.parse_auth_response_url`, mirroring that method in
|
- `SpotifyPKCE.parse_auth_response_url`, mirroring that method in
|
||||||
`SpotifyOAuth`
|
`SpotifyOAuth`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -249,7 +271,7 @@ Add your changes below.
|
|||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Using `SpotifyPKCE.get_authorization_url` will now generate a code
|
- Using `SpotifyPKCE.get_authorization_url` will now generate a code
|
||||||
challenge if needed
|
challenge if needed
|
||||||
|
|
||||||
## [2.14.0] - 2020-08-29
|
## [2.14.0] - 2020-08-29
|
||||||
|
|
||||||
@ -257,9 +279,9 @@ Add your changes below.
|
|||||||
|
|
||||||
- (experimental) Support to search multiple/all markets at once.
|
- (experimental) Support to search multiple/all markets at once.
|
||||||
- Support to test whether the current user is following certain
|
- Support to test whether the current user is following certain
|
||||||
users or artists
|
users or artists
|
||||||
- Proper replacements for all deprecated playlist endpoints
|
- Proper replacements for all deprecated playlist endpoints
|
||||||
(See https://developer.spotify.com/community/news/2018/06/12/changes-to-playlist-uris/ and below)
|
(See https://developer.spotify.com/community/news/2018/06/12/changes-to-playlist-uris/ and below)
|
||||||
- Allow for OAuth 2.0 authorization by instructing the user to open the URL in a browser instead of opening the browser.
|
- Allow for OAuth 2.0 authorization by instructing the user to open the URL in a browser instead of opening the browser.
|
||||||
- Reason for 403 error in SpotifyException
|
- Reason for 403 error in SpotifyException
|
||||||
- Support for the PKCE Auth Flow
|
- Support for the PKCE Auth Flow
|
||||||
@ -277,11 +299,11 @@ Add your changes below.
|
|||||||
- `user_playlist_replace_tracks` in favor of `playlist_replace_items`
|
- `user_playlist_replace_tracks` in favor of `playlist_replace_items`
|
||||||
- `user_playlist_reorder_tracks` in favor of `playlist_reorder_items`
|
- `user_playlist_reorder_tracks` in favor of `playlist_reorder_items`
|
||||||
- `user_playlist_remove_all_occurrences_of_tracks` in favor of
|
- `user_playlist_remove_all_occurrences_of_tracks` in favor of
|
||||||
`playlist_remove_all_occurrences_of_items`
|
`playlist_remove_all_occurrences_of_items`
|
||||||
- `user_playlist_remove_specific_occurrences_of_tracks` in favor of
|
- `user_playlist_remove_specific_occurrences_of_tracks` in favor of
|
||||||
`playlist_remove_specific_occurrences_of_items`
|
`playlist_remove_specific_occurrences_of_items`
|
||||||
- `user_playlist_follow_playlist` in favor of
|
- `user_playlist_follow_playlist` in favor of
|
||||||
`current_user_follow_playlist`
|
`current_user_follow_playlist`
|
||||||
- `user_playlist_is_following` in favor of `playlist_is_following`
|
- `user_playlist_is_following` in favor of `playlist_is_following`
|
||||||
- `playlist_tracks` in favor of `playlist_items`
|
- `playlist_tracks` in favor of `playlist_items`
|
||||||
|
|
||||||
@ -294,12 +316,12 @@ Add your changes below.
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `SpotifyImplicitGrant` as an auth manager option. It provides
|
- Added `SpotifyImplicitGrant` as an auth manager option. It provides
|
||||||
user authentication without a client secret but sacrifices the ability
|
user authentication without a client secret but sacrifices the ability
|
||||||
to refresh the token without user input. (However, read the class
|
to refresh the token without user input. (However, read the class
|
||||||
docstring for security advisory.)
|
docstring for security advisory.)
|
||||||
- Added built-in verification of the `state` query parameter
|
- Added built-in verification of the `state` query parameter
|
||||||
- Added two new attributes: error and error_description to `SpotifyOauthError` exception class to show
|
- Added two new attributes: error and error_description to `SpotifyOauthError` exception class to show
|
||||||
authorization/authentication web api errors details.
|
authorization/authentication web api errors details.
|
||||||
- Added `SpotifyStateError` subclass of `SpotifyOauthError`
|
- Added `SpotifyStateError` subclass of `SpotifyOauthError`
|
||||||
- Allow extending `SpotifyClientCredentials` and `SpotifyOAuth`
|
- Allow extending `SpotifyClientCredentials` and `SpotifyOAuth`
|
||||||
- Added the market parameter to `album_tracks`
|
- Added the market parameter to `album_tracks`
|
||||||
@ -323,10 +345,10 @@ Add your changes below.
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Updated the documentation to give more details on the authorization process and reflect
|
- Updated the documentation to give more details on the authorization process and reflect
|
||||||
2020 Spotify Application jargon and practices.
|
2020 Spotify Application jargon and practices.
|
||||||
|
|
||||||
- The local webserver is only started for localhost redirect_uri which specify a port,
|
- The local webserver is only started for localhost redirect_uri which specify a port,
|
||||||
i.e. it is started for `http://localhost:8080` or `http://127.0.0.1:8080`, not for `http://localhost`.
|
i.e. it is started for `http://localhost:8080` or `http://127.0.0.1:8080`, not for `http://localhost`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@ -349,10 +371,10 @@ Add your changes below.
|
|||||||
|
|
||||||
- Client retry logic has changed as it now uses urllib3's `Retry` in conjunction with requests `Session`
|
- Client retry logic has changed as it now uses urllib3's `Retry` in conjunction with requests `Session`
|
||||||
- The session is customizable as it allows for:
|
- The session is customizable as it allows for:
|
||||||
- status_forcelist
|
- status_forcelist
|
||||||
- retries
|
- retries
|
||||||
- status_retries
|
- status_retries
|
||||||
- backoff_factor
|
- backoff_factor
|
||||||
- Spin up a local webserver to autofill authentication URL
|
- Spin up a local webserver to autofill authentication URL
|
||||||
- Use session in SpotifyAuthBase
|
- Use session in SpotifyAuthBase
|
||||||
- Logging used instead of print statements
|
- Logging used instead of print statements
|
||||||
@ -367,9 +389,9 @@ Add your changes below.
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support for `add_to_queue`
|
- Support for `add_to_queue`
|
||||||
- **Parameters:**
|
- **Parameters:**
|
||||||
- track uri, id, or url
|
- track uri, id, or url
|
||||||
- device id. If None, then the active device is used.
|
- device id. If None, then the active device is used.
|
||||||
- Add CHANGELOG and LICENSE to released package
|
- Add CHANGELOG and LICENSE to released package
|
||||||
|
|
||||||
## [2.9.0] - 2020-02-15
|
## [2.9.0] - 2020-02-15
|
||||||
@ -440,7 +462,7 @@ Add your changes below.
|
|||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed inconsistent behaviour with some API methods when
|
- Fixed inconsistent behaviour with some API methods when
|
||||||
a full HTTP URL is passed.
|
a full HTTP URL is passed.
|
||||||
- Fixed invalid calls to logging warn method
|
- Fixed invalid calls to logging warn method
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
@ -466,6 +488,7 @@ Add your changes below.
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Made instructions in the CONTRIBUTING.md file more clear such that it is easier to onboard and there are no conflicts with TUTORIAL.md
|
- Made instructions in the CONTRIBUTING.md file more clear such that it is easier to onboard and there are no conflicts with TUTORIAL.md
|
||||||
|
|
||||||
## [2.5.0] - 2020-01-11
|
## [2.5.0] - 2020-01-11
|
||||||
|
|
||||||
Added follow and player endpoints
|
Added follow and player endpoints
|
||||||
|
|||||||
1
examples
1
examples
@ -1 +0,0 @@
|
|||||||
Subproject commit c610a79705ef4aa55e4d61572a012f77b6f7245d
|
|
||||||
@ -41,7 +41,6 @@ class CacheHandler():
|
|||||||
Save a token_info dictionary object to the cache and return None.
|
Save a token_info dictionary object to the cache and return None.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class CacheFileHandler(CacheHandler):
|
class CacheFileHandler(CacheHandler):
|
||||||
@ -77,24 +76,24 @@ class CacheFileHandler(CacheHandler):
|
|||||||
token_info = None
|
token_info = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f = open(self.cache_path)
|
with open(self.cache_path, encoding='utf-8') as f:
|
||||||
token_info_string = f.read()
|
token_info_string = f.read()
|
||||||
f.close()
|
|
||||||
token_info = json.loads(token_info_string)
|
token_info = json.loads(token_info_string)
|
||||||
|
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
if error.errno == errno.ENOENT:
|
if error.errno == errno.ENOENT:
|
||||||
logger.debug(f"cache does not exist at: {self.cache_path}")
|
logger.debug(f"cache does not exist at: {self.cache_path}")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Couldn't read cache at: {self.cache_path}")
|
logger.warning("Couldn't read cache at: %s", self.cache_path)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.warning("Couldn't decode JSON from cache at: %s", self.cache_path)
|
||||||
|
|
||||||
return token_info
|
return token_info
|
||||||
|
|
||||||
def save_token_to_cache(self, token_info):
|
def save_token_to_cache(self, token_info):
|
||||||
try:
|
try:
|
||||||
f = open(self.cache_path, "w")
|
with open(self.cache_path, "w", encoding='utf-8') as f:
|
||||||
f.write(json.dumps(token_info, cls=self.encoder_cls))
|
f.write(json.dumps(token_info, cls=self.encoder_cls))
|
||||||
f.close()
|
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.warning(f"Couldn't write token to cache at: {self.cache_path}")
|
logger.warning(f"Couldn't write token to cache at: {self.cache_path}")
|
||||||
|
|
||||||
@ -214,6 +213,7 @@ class RedisCacheHandler(CacheHandler):
|
|||||||
class MemcacheCacheHandler(CacheHandler):
|
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=None) -> None:
|
def __init__(self, memcache, key=None) -> None:
|
||||||
"""
|
"""
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|||||||
@ -153,6 +153,7 @@ class Retry(urllib3.Retry):
|
|||||||
"""
|
"""
|
||||||
Custom class for printing a warning when a rate/request limit is reached.
|
Custom class for printing a warning when a rate/request limit is reached.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def increment(
|
def increment(
|
||||||
self,
|
self,
|
||||||
method: str | None = None,
|
method: str | None = None,
|
||||||
|
|||||||
@ -52,18 +52,21 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
@patch('spotipy.cache_handler.open', create=True)
|
@patch('spotipy.cache_handler.open', create=True)
|
||||||
def test_gets_from_cache_path(self, opener,
|
def test_gets_from_cache_path(self, opener,
|
||||||
is_token_expired, refresh_access_token):
|
is_token_expired, refresh_access_token):
|
||||||
|
"""Test that the token is retrieved from the cache path."""
|
||||||
scope = "playlist-modify-private"
|
scope = "playlist-modify-private"
|
||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
tok = _make_fake_token(1, 1, scope)
|
tok = _make_fake_token(1, 1, scope)
|
||||||
|
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
opener.return_value = token_file
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_oauth(scope, path)
|
spot = _make_oauth(scope, path)
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
cached_tok_legacy = spot.get_cached_token()
|
cached_tok_legacy = spot.get_cached_token()
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path, encoding='utf-8')
|
||||||
self.assertIsNotNone(cached_tok)
|
self.assertIsNotNone(cached_tok)
|
||||||
self.assertIsNotNone(cached_tok_legacy)
|
self.assertIsNotNone(cached_tok_legacy)
|
||||||
self.assertEqual(refresh_access_token.call_count, 0)
|
self.assertEqual(refresh_access_token.call_count, 0)
|
||||||
@ -73,13 +76,15 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
@patch('spotipy.cache_handler.open', create=True)
|
@patch('spotipy.cache_handler.open', create=True)
|
||||||
def test_expired_token_refreshes(self, opener,
|
def test_expired_token_refreshes(self, opener,
|
||||||
is_token_expired, refresh_access_token):
|
is_token_expired, refresh_access_token):
|
||||||
|
"""Test that an expired token is refreshed."""
|
||||||
scope = "playlist-modify-private"
|
scope = "playlist-modify-private"
|
||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
expired_tok = _make_fake_token(0, None, scope)
|
expired_tok = _make_fake_token(0, None, scope)
|
||||||
fresh_tok = _make_fake_token(1, 1, scope)
|
fresh_tok = _make_fake_token(1, 1, scope)
|
||||||
|
|
||||||
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
||||||
opener.return_value = token_file
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
refresh_access_token.return_value = fresh_tok
|
refresh_access_token.return_value = fresh_tok
|
||||||
|
|
||||||
spot = _make_oauth(scope, path)
|
spot = _make_oauth(scope, path)
|
||||||
@ -87,7 +92,7 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
|
|
||||||
is_token_expired.assert_called_with(expired_tok)
|
is_token_expired.assert_called_with(expired_tok)
|
||||||
refresh_access_token.assert_called_with(expired_tok['refresh_token'])
|
refresh_access_token.assert_called_with(expired_tok['refresh_token'])
|
||||||
opener.assert_any_call(path)
|
opener.assert_any_call(path, encoding='utf-8')
|
||||||
|
|
||||||
@patch.multiple(SpotifyOAuth,
|
@patch.multiple(SpotifyOAuth,
|
||||||
is_token_expired=DEFAULT, refresh_access_token=DEFAULT)
|
is_token_expired=DEFAULT, refresh_access_token=DEFAULT)
|
||||||
@ -99,29 +104,35 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
tok = _make_fake_token(1, 1, token_scope)
|
tok = _make_fake_token(1, 1, token_scope)
|
||||||
|
|
||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
|
opener.return_value = token_file
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_oauth(requested_scope, path)
|
spot = _make_oauth(requested_scope, path)
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path, encoding='utf-8')
|
||||||
self.assertIsNone(cached_tok)
|
self.assertIsNone(cached_tok)
|
||||||
self.assertEqual(refresh_access_token.call_count, 0)
|
self.assertEqual(refresh_access_token.call_count, 0)
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
@patch('spotipy.cache_handler.open', create=True)
|
||||||
def test_saves_to_cache_path(self, opener):
|
def test_saves_to_cache_path(self, opener):
|
||||||
|
"""Test that the token is saved to the cache path."""
|
||||||
scope = "playlist-modify-private"
|
scope = "playlist-modify-private"
|
||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
tok = _make_fake_token(1, 1, scope)
|
tok = _make_fake_token(1, 1, scope)
|
||||||
|
|
||||||
fi = _fake_file()
|
fi = _fake_file()
|
||||||
opener.return_value = fi
|
opener.return_value = fi
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
|
|
||||||
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, path)
|
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, path)
|
||||||
spot.cache_handler.save_token_to_cache(tok)
|
spot.cache_handler.save_token_to_cache(tok)
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||||
self.assertTrue(fi.write.called)
|
self.assertTrue(fi.write.called)
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
@patch('spotipy.cache_handler.open', create=True)
|
||||||
@ -132,11 +143,13 @@ class OAuthCacheTest(unittest.TestCase):
|
|||||||
|
|
||||||
fi = _fake_file()
|
fi = _fake_file()
|
||||||
opener.return_value = fi
|
opener.return_value = fi
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
|
|
||||||
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, path)
|
spot = SpotifyOAuth("CLID", "CLISEC", "REDIR", "STATE", scope, path)
|
||||||
spot._save_token_info(tok)
|
spot._save_token_info(tok)
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||||
self.assertTrue(fi.write.called)
|
self.assertTrue(fi.write.called)
|
||||||
|
|
||||||
def test_cache_handler(self):
|
def test_cache_handler(self):
|
||||||
@ -252,32 +265,38 @@ class ImplicitGrantCacheTest(unittest.TestCase):
|
|||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
tok = _make_fake_token(1, 1, scope)
|
tok = _make_fake_token(1, 1, scope)
|
||||||
|
|
||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
|
opener.return_value = token_file
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_implicitgrantauth(scope, path)
|
spot = _make_implicitgrantauth(scope, path)
|
||||||
cached_tok = spot.cache_handler.get_cached_token()
|
cached_tok = spot.cache_handler.get_cached_token()
|
||||||
cached_tok_legacy = spot.get_cached_token()
|
cached_tok_legacy = spot.get_cached_token()
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path, encoding='utf-8')
|
||||||
self.assertIsNotNone(cached_tok)
|
self.assertIsNotNone(cached_tok)
|
||||||
self.assertIsNotNone(cached_tok_legacy)
|
self.assertIsNotNone(cached_tok_legacy)
|
||||||
|
|
||||||
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
@patch('spotipy.cache_handler.open', create=True)
|
||||||
def test_expired_token_returns_none(self, opener, is_token_expired):
|
def test_expired_token_returns_none(self, opener, is_token_expired):
|
||||||
|
"""Test that an expired token returns None."""
|
||||||
scope = "playlist-modify-private"
|
scope = "playlist-modify-private"
|
||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
expired_tok = _make_fake_token(0, None, scope)
|
expired_tok = _make_fake_token(0, None, scope)
|
||||||
|
|
||||||
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
||||||
opener.return_value = token_file
|
opener.return_value = token_file
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
|
|
||||||
spot = _make_implicitgrantauth(scope, path)
|
spot = _make_implicitgrantauth(scope, path)
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
|
|
||||||
is_token_expired.assert_called_with(expired_tok)
|
is_token_expired.assert_called_with(expired_tok)
|
||||||
opener.assert_any_call(path)
|
opener.assert_any_call(path, encoding='utf-8')
|
||||||
self.assertIsNone(cached_tok)
|
self.assertIsNone(cached_tok)
|
||||||
|
|
||||||
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
@patch.object(SpotifyImplicitGrant, "is_token_expired", DEFAULT)
|
||||||
@ -288,13 +307,16 @@ class ImplicitGrantCacheTest(unittest.TestCase):
|
|||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
tok = _make_fake_token(1, 1, token_scope)
|
tok = _make_fake_token(1, 1, token_scope)
|
||||||
|
|
||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
|
opener.return_value = token_file
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_implicitgrantauth(requested_scope, path)
|
spot = _make_implicitgrantauth(requested_scope, path)
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path, encoding='utf-8')
|
||||||
self.assertIsNone(cached_tok)
|
self.assertIsNone(cached_tok)
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
@patch('spotipy.cache_handler.open', create=True)
|
||||||
@ -306,10 +328,12 @@ class ImplicitGrantCacheTest(unittest.TestCase):
|
|||||||
fi = _fake_file()
|
fi = _fake_file()
|
||||||
opener.return_value = fi
|
opener.return_value = fi
|
||||||
|
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
spot = SpotifyImplicitGrant("CLID", "REDIR", "STATE", scope, path)
|
spot = SpotifyImplicitGrant("CLID", "REDIR", "STATE", scope, path)
|
||||||
spot.cache_handler.save_token_to_cache(tok)
|
spot.cache_handler.save_token_to_cache(tok)
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||||
self.assertTrue(fi.write.called)
|
self.assertTrue(fi.write.called)
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
@patch('spotipy.cache_handler.open', create=True)
|
||||||
@ -320,11 +344,13 @@ class ImplicitGrantCacheTest(unittest.TestCase):
|
|||||||
|
|
||||||
fi = _fake_file()
|
fi = _fake_file()
|
||||||
opener.return_value = fi
|
opener.return_value = fi
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
|
|
||||||
spot = SpotifyImplicitGrant("CLID", "REDIR", "STATE", scope, path)
|
spot = SpotifyImplicitGrant("CLID", "REDIR", "STATE", scope, path)
|
||||||
spot._save_token_info(tok)
|
spot._save_token_info(tok)
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||||
self.assertTrue(fi.write.called)
|
self.assertTrue(fi.write.called)
|
||||||
|
|
||||||
|
|
||||||
@ -389,14 +415,17 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
tok = _make_fake_token(1, 1, scope)
|
tok = _make_fake_token(1, 1, scope)
|
||||||
|
|
||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
|
opener.return_value = token_file
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_pkceauth(scope, path)
|
spot = _make_pkceauth(scope, path)
|
||||||
cached_tok = spot.cache_handler.get_cached_token()
|
cached_tok = spot.cache_handler.get_cached_token()
|
||||||
cached_tok_legacy = spot.get_cached_token()
|
cached_tok_legacy = spot.get_cached_token()
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path, encoding='utf-8')
|
||||||
self.assertIsNotNone(cached_tok)
|
self.assertIsNotNone(cached_tok)
|
||||||
self.assertIsNotNone(cached_tok_legacy)
|
self.assertIsNotNone(cached_tok_legacy)
|
||||||
self.assertEqual(refresh_access_token.call_count, 0)
|
self.assertEqual(refresh_access_token.call_count, 0)
|
||||||
@ -412,7 +441,8 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
fresh_tok = _make_fake_token(1, 1, scope)
|
fresh_tok = _make_fake_token(1, 1, scope)
|
||||||
|
|
||||||
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
token_file = _token_file(json.dumps(expired_tok, ensure_ascii=False))
|
||||||
opener.return_value = token_file
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
refresh_access_token.return_value = fresh_tok
|
refresh_access_token.return_value = fresh_tok
|
||||||
|
|
||||||
spot = _make_pkceauth(scope, path)
|
spot = _make_pkceauth(scope, path)
|
||||||
@ -420,7 +450,7 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
|
|
||||||
is_token_expired.assert_called_with(expired_tok)
|
is_token_expired.assert_called_with(expired_tok)
|
||||||
refresh_access_token.assert_called_with(expired_tok['refresh_token'])
|
refresh_access_token.assert_called_with(expired_tok['refresh_token'])
|
||||||
opener.assert_any_call(path)
|
opener.assert_any_call(path, encoding='utf-8')
|
||||||
|
|
||||||
@patch.multiple(SpotifyPKCE,
|
@patch.multiple(SpotifyPKCE,
|
||||||
is_token_expired=DEFAULT, refresh_access_token=DEFAULT)
|
is_token_expired=DEFAULT, refresh_access_token=DEFAULT)
|
||||||
@ -432,13 +462,16 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
path = ".cache-username"
|
path = ".cache-username"
|
||||||
tok = _make_fake_token(1, 1, token_scope)
|
tok = _make_fake_token(1, 1, token_scope)
|
||||||
|
|
||||||
opener.return_value = _token_file(json.dumps(tok, ensure_ascii=False))
|
token_file = _token_file(json.dumps(tok, ensure_ascii=False))
|
||||||
|
opener.return_value = token_file
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=token_file)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
is_token_expired.return_value = False
|
is_token_expired.return_value = False
|
||||||
|
|
||||||
spot = _make_pkceauth(requested_scope, path)
|
spot = _make_pkceauth(requested_scope, path)
|
||||||
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
cached_tok = spot.validate_token(spot.cache_handler.get_cached_token())
|
||||||
|
|
||||||
opener.assert_called_with(path)
|
opener.assert_called_with(path, encoding='utf-8')
|
||||||
self.assertIsNone(cached_tok)
|
self.assertIsNone(cached_tok)
|
||||||
self.assertEqual(refresh_access_token.call_count, 0)
|
self.assertEqual(refresh_access_token.call_count, 0)
|
||||||
|
|
||||||
@ -450,11 +483,12 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
|
|
||||||
fi = _fake_file()
|
fi = _fake_file()
|
||||||
opener.return_value = fi
|
opener.return_value = fi
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, path)
|
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, path)
|
||||||
spot.cache_handler.save_token_to_cache(tok)
|
spot.cache_handler.save_token_to_cache(tok)
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||||
self.assertTrue(fi.write.called)
|
self.assertTrue(fi.write.called)
|
||||||
|
|
||||||
@patch('spotipy.cache_handler.open', create=True)
|
@patch('spotipy.cache_handler.open', create=True)
|
||||||
@ -465,11 +499,13 @@ class SpotifyPKCECacheTest(unittest.TestCase):
|
|||||||
|
|
||||||
fi = _fake_file()
|
fi = _fake_file()
|
||||||
opener.return_value = fi
|
opener.return_value = fi
|
||||||
|
opener.return_value.__enter__ = mock.Mock(return_value=fi)
|
||||||
|
opener.return_value.__exit__ = mock.Mock(return_value=False)
|
||||||
|
|
||||||
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, path)
|
spot = SpotifyPKCE("CLID", "REDIR", "STATE", scope, path)
|
||||||
spot._save_token_info(tok)
|
spot._save_token_info(tok)
|
||||||
|
|
||||||
opener.assert_called_with(path, 'w')
|
opener.assert_called_with(path, 'w', encoding='utf-8')
|
||||||
self.assertTrue(fi.write.called)
|
self.assertTrue(fi.write.called)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user