From be587b5bc67fc8225e1bc10b54bd79b3d4d193de Mon Sep 17 00:00:00 2001 From: Robert Kaye Date: Tue, 21 Jan 2025 12:36:41 +0100 Subject: [PATCH 1/4] Add the retry session/wrapper --- liblistenbrainz/client.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/liblistenbrainz/client.py b/liblistenbrainz/client.py index 96ca8a1..de1b336 100644 --- a/liblistenbrainz/client.py +++ b/liblistenbrainz/client.py @@ -16,7 +16,9 @@ import json import requests +from requests.adapters import HTTPAdapter import time +from urllib3.util import Retry from datetime import datetime from enum import Enum @@ -25,6 +27,9 @@ from liblistenbrainz.utils import _validate_submit_listens_payload, _convert_api_payload_to_listen from urllib.parse import urljoin +retry_strategy = Retry(total=5, status_forcelist=[429, 500, 502, 503, 504]) +adapter = HTTPAdapter(max_retries=retry_strategy) + STATS_SUPPORTED_TIME_RANGES = ( 'week', 'month', @@ -95,9 +100,13 @@ def _get(self, endpoint, params=None, headers=None): if self._auth_token: headers['Authorization'] = f'Token {self._auth_token}' + session = requests.Session() + session.mount("http://", adapter) # http is not used, but in case someone needs to use to for dev work, its included here + session.mount("https://", adapter) + try: self._wait_until_rate_limit() - response = requests.get( + response = session.get( urljoin(API_BASE_URL, endpoint), params=params, headers=headers, From 396cd8b797ecfb780bf55817755dc3386465e88a Mon Sep 17 00:00:00 2001 From: Robert Kaye Date: Tue, 21 Jan 2025 13:08:06 +0100 Subject: [PATCH 2/4] Fix tests --- pyproject.toml | 4 ++-- tests/test_client.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cc6b3b4..c9ac0a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,8 +19,8 @@ dependencies = [ [project.optional-dependencies] tests = [ - 'pytest == 5.4.1', - 'pytest-cov == 2.8.1' + 'pytest == 8.3.4', + 'pytest-cov == 6.0.0' ] build = [ 'build', diff --git a/tests/test_client.py b/tests/test_client.py index 84726e7..abbf5dd 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -33,7 +33,7 @@ class ListenBrainzClientTestCase(unittest.TestCase): def setUp(self): self.client = liblistenbrainz.ListenBrainz() - @mock.patch('liblistenbrainz.client.requests.get') + @mock.patch('liblistenbrainz.client.requests.Session.get') def test_get_injects_auth_token_if_available(self, mock_requests_get): mock_requests_get.return_value = mock.MagicMock() self.client._get('/1/user/iliekcomputers/listens') @@ -283,7 +283,7 @@ def test_post_api_exceptions(self, mock_requests_post): with self.assertRaises(errors.ListenBrainzAPIException): self.client.submit_single_listen(listen) - @mock.patch('liblistenbrainz.client.requests.get') + @mock.patch('liblistenbrainz.client.requests.Session.get') def test_get_api_exceptions(self, mock_requests_get): response = mock.MagicMock() response.json.return_value = {'code': 401, 'error': 'Unauthorized'} @@ -295,7 +295,7 @@ def test_get_api_exceptions(self, mock_requests_get): self.client.get_listens('iliekcomputers') - @mock.patch('liblistenbrainz.client.requests.get') + @mock.patch('liblistenbrainz.client.requests.Session.get') def test_get_user_recommendation_recordings(self, mock_requests_get): mock_requests_get.return_value = mock.MagicMock() with self.assertRaises(ValueError): From 51751063c6610a68153822da50dec386e48b4a2b Mon Sep 17 00:00:00 2001 From: Robert Kaye Date: Tue, 21 Jan 2025 13:11:22 +0100 Subject: [PATCH 3/4] Lower pytest-cov --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c9ac0a2..ab15d94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ [project.optional-dependencies] tests = [ 'pytest == 8.3.4', - 'pytest-cov == 6.0.0' + 'pytest-cov >= 5.0.0' ] build = [ 'build', From 3e15652fa239e0c25c77e83a030a47b9100e2209 Mon Sep 17 00:00:00 2001 From: Robert Kaye Date: Tue, 21 Jan 2025 15:01:46 +0100 Subject: [PATCH 4/4] Add post support --- liblistenbrainz/client.py | 9 +++++++-- tests/test_client.py | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/liblistenbrainz/client.py b/liblistenbrainz/client.py index de1b336..d734448 100644 --- a/liblistenbrainz/client.py +++ b/liblistenbrainz/client.py @@ -27,7 +27,7 @@ from liblistenbrainz.utils import _validate_submit_listens_payload, _convert_api_payload_to_listen from urllib.parse import urljoin -retry_strategy = Retry(total=5, status_forcelist=[429, 500, 502, 503, 504]) +retry_strategy = Retry(total=5, allowed_methods=('GET', 'POST'), status_forcelist=[429, 500, 502, 503, 504]) adapter = HTTPAdapter(max_retries=retry_strategy) STATS_SUPPORTED_TIME_RANGES = ( @@ -133,9 +133,14 @@ def _post(self, endpoint, data=None, headers=None): headers = {} if self._auth_token: headers['Authorization'] = f'Token {self._auth_token}' + + session = requests.Session() + session.mount("http://", adapter) # http is not used, but in case someone needs to use to for dev work, its included here + session.mount("https://", adapter) + try: self._wait_until_rate_limit() - response = requests.post( + response = session.post( urljoin(API_BASE_URL, endpoint), data=data, headers=headers, diff --git a/tests/test_client.py b/tests/test_client.py index abbf5dd..7686fc1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -55,7 +55,7 @@ def test_get_injects_auth_token_if_available(self, mock_requests_get): ) - @mock.patch('liblistenbrainz.client.requests.post') + @mock.patch('liblistenbrainz.client.requests.Session.post') def test_post_injects_auth_token_if_available(self, mock_requests_post): mock_requests_post.return_value = mock.MagicMock() self.client._post('/1/user/iliekcomputers/listens') @@ -169,7 +169,7 @@ def test_client_get_playing_now_no_listen(self): ) self.assertIsNone(received_listen) - @mock.patch('liblistenbrainz.client.requests.post') + @mock.patch('liblistenbrainz.client.requests.Session.post') @mock.patch('liblistenbrainz.client.json.dumps') def test_submit_single_listen(self, mock_json_dumps, mock_requests_post): ts = int(time.time())