diff --git a/CHANGELOG.md b/CHANGELOG.md index fc92c28..f44e6e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Add your changes below. ### Added +- Added a warning when a search query is longer than the 100 character limit Spotify allows ### Fixed diff --git a/spotipy/client.py b/spotipy/client.py index e7bd2b2..d4e7750 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -33,6 +33,8 @@ class Spotify: """ max_retries = 3 default_retry_codes = (429, 500, 502, 503, 504) + # Spotify rejects search queries longer than this many characters. + max_search_query_length = 100 country_codes = [ "AD", "AR", @@ -610,6 +612,7 @@ class Spotify: - market - An ISO 3166-1 alpha-2 country code or the string from_token. """ + self._warn_if_query_too_long(q) return self._get( "search", q=q, limit=limit, offset=offset, type=type, market=market ) @@ -644,8 +647,22 @@ class Spotify: "Searching multiple markets is poorly performing.", UserWarning, ) + self._warn_if_query_too_long(q) return self._search_multiple_markets(q, limit, offset, type, markets, total) + def _warn_if_query_too_long(self, q): + """ Warn, without blocking, when a search query is longer than the + Spotify API allows. Spotify returns an error for queries over + ``max_search_query_length`` characters, so this points at the + likely cause instead of letting the request fail silently. + """ + if isinstance(q, str) and len(q) > self.max_search_query_length: + logger.warning( + f"Search query is {len(q)} characters long, which is over the " + f"{self.max_search_query_length} character limit that Spotify " + f"allows. The request may be rejected." + ) + def user(self, user): """ Gets basic profile information about a Spotify User diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py new file mode 100644 index 0000000..7e94de3 --- /dev/null +++ b/tests/unit/test_client.py @@ -0,0 +1,32 @@ +import unittest +import unittest.mock as mock + +from spotipy import Spotify + + +class SearchQueryLengthTest(unittest.TestCase): + + def setUp(self): + self.spotify = Spotify(auth="TOKEN") + + @mock.patch.object(Spotify, "_internal_call", return_value={}) + def test_long_query_warns(self, _internal_call): + query = "a" * (Spotify.max_search_query_length + 1) + with self.assertLogs("spotipy.client", level="WARNING") as logs: + self.spotify.search(query) + self.assertTrue( + any("character limit" in message or "character" in message + for message in logs.output) + ) + + @mock.patch.object(Spotify, "_internal_call", return_value={}) + def test_short_query_does_not_warn(self, _internal_call): + query = "a" * Spotify.max_search_query_length + logger = "spotipy.client" + with self.assertRaises(AssertionError): + with self.assertLogs(logger, level="WARNING"): + self.spotify.search(query) + + +if __name__ == "__main__": + unittest.main()