Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-proton-vpn-api-core for openSUSE:Factory checked in at 2025-04-17 16:10:14 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-proton-vpn-api-core (Old) and /work/SRC/openSUSE:Factory/.python-proton-vpn-api-core.new.30101 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-proton-vpn-api-core" Thu Apr 17 16:10:14 2025 rev:8 rq:1270228 version:0.42.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-proton-vpn-api-core/python-proton-vpn-api-core.changes 2025-04-02 17:12:15.286558967 +0200 +++ /work/SRC/openSUSE:Factory/.python-proton-vpn-api-core.new.30101/python-proton-vpn-api-core.changes 2025-04-20 20:08:01.813266380 +0200 @@ -1,0 +2,7 @@ +Mon Apr 14 10:57:23 UTC 2025 - Richard Rahl <rra...@opensuse.org> + +- update to 0.42.4: + * fix: [VPNLINUX-1191] swallow&re-schedule server refresh when we receive HTTP code 429 + * fix: [VPNLINUX-1191] swallow&re-schedule when we receive HTTP code 429 from refreshers + +------------------------------------------------------------------- Old: ---- v0.42.3.tar.gz New: ---- v0.42.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-proton-vpn-api-core.spec ++++++ --- /var/tmp/diff_new_pack.cSuHT1/_old 2025-04-20 20:08:02.557297651 +0200 +++ /var/tmp/diff_new_pack.cSuHT1/_new 2025-04-20 20:08:02.557297651 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-proton-vpn-api-core -Version: 0.42.3 +Version: 0.42.4 Release: 0 Summary: Proton VPN API library License: GPL-3.0-or-later ++++++ v0.42.3.tar.gz -> v0.42.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/proton/vpn/core/refresher/certificate_refresher.py new/python-proton-vpn-api-core-0.42.4/proton/vpn/core/refresher/certificate_refresher.py --- old/python-proton-vpn-api-core-0.42.3/proton/vpn/core/refresher/certificate_refresher.py 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/proton/vpn/core/refresher/certificate_refresher.py 2025-04-07 13:04:16.000000000 +0200 @@ -27,6 +27,7 @@ from proton.vpn.session.credentials import VPNPubkeyCredentials from proton.session.exceptions import ( ProtonAPINotReachable, ProtonAPINotAvailable, + ProtonAPIError ) logger = logging.getLogger(__name__) @@ -64,6 +65,13 @@ next_refresh_delay = certificate.remaining_time_to_next_refresh self._number_of_failed_refresh_attempts = 0 await self._notify() + except ProtonAPIError as error: + if error.http_code != 429: + raise + + logger.warning(f"Certificate refresh failed {error}") + next_refresh_delay = self._get_next_refresh_delay() + self._number_of_failed_refresh_attempts += 1 except (ProtonAPINotReachable, ProtonAPINotAvailable) as error: logger.warning(f"Certificate refresh failed: {error}") next_refresh_delay = self._get_next_refresh_delay() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/proton/vpn/core/refresher/client_config_refresher.py new/python-proton-vpn-api-core-0.42.4/proton/vpn/core/refresher/client_config_refresher.py --- old/python-proton-vpn-api-core-0.42.3/proton/vpn/core/refresher/client_config_refresher.py 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/proton/vpn/core/refresher/client_config_refresher.py 2025-04-07 13:04:16.000000000 +0200 @@ -25,6 +25,7 @@ from proton.vpn import logging from proton.session.exceptions import ( ProtonAPINotReachable, ProtonAPINotAvailable, + ProtonAPIError ) logger = logging.getLogger(__name__) @@ -54,6 +55,12 @@ try: new_client_config = await self._session.fetch_client_config() next_refresh_delay = new_client_config.seconds_until_expiration + except ProtonAPIError as error: + if error.http_code != 429: + raise + + logger.warning(f"Client config refresh failed: {error}") + next_refresh_delay = ClientConfig.get_refresh_interval_in_seconds() except (ProtonAPINotReachable, ProtonAPINotAvailable) as error: logger.warning(f"Client config refresh failed: {error}") next_refresh_delay = ClientConfig.get_refresh_interval_in_seconds() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/proton/vpn/core/refresher/feature_flags_refresher.py new/python-proton-vpn-api-core-0.42.4/proton/vpn/core/refresher/feature_flags_refresher.py --- old/python-proton-vpn-api-core-0.42.3/proton/vpn/core/refresher/feature_flags_refresher.py 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/proton/vpn/core/refresher/feature_flags_refresher.py 2025-04-07 13:04:16.000000000 +0200 @@ -25,6 +25,7 @@ from proton.vpn import logging from proton.session.exceptions import ( ProtonAPINotReachable, ProtonAPINotAvailable, + ProtonAPIError ) logger = logging.getLogger(__name__) @@ -53,6 +54,12 @@ try: feature_flags = await self._session.fetch_feature_flags() next_refresh_delay = feature_flags.seconds_until_expiration + except ProtonAPIError as error: + if error.http_code != 429: + raise + + logger.warning(f"Feature flag refresh failed {error}") + next_refresh_delay = FeatureFlags.get_refresh_interval_in_seconds() except (ProtonAPINotReachable, ProtonAPINotAvailable) as error: logger.warning(f"Feature flag refresh failed: {error}") next_refresh_delay = FeatureFlags.get_refresh_interval_in_seconds() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/proton/vpn/core/refresher/server_list_refresher.py new/python-proton-vpn-api-core-0.42.4/proton/vpn/core/refresher/server_list_refresher.py --- old/python-proton-vpn-api-core-0.42.3/proton/vpn/core/refresher/server_list_refresher.py 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/proton/vpn/core/refresher/server_list_refresher.py 2025-04-07 13:04:16.000000000 +0200 @@ -21,6 +21,7 @@ from proton.session.exceptions import ( ProtonAPINotReachable, ProtonAPINotAvailable, + ProtonAPIError ) from proton.vpn import logging @@ -62,6 +63,12 @@ next_refresh_delay = server_list.seconds_until_expiration else: next_refresh_delay = self._session.server_list.seconds_until_expiration + except ProtonAPIError as error: + if error.http_code != 429: + raise + + logger.warning(f"Server list refresh failed: {error}") + next_refresh_delay = ServerList.get_loads_refresh_interval_in_seconds() except (ProtonAPINotReachable, ProtonAPINotAvailable) as error: logger.warning(f"Server list refresh failed: {error}") next_refresh_delay = ServerList.get_loads_refresh_interval_in_seconds() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/tests/core/refresher/test_certificate_refresher.py new/python-proton-vpn-api-core-0.42.4/tests/core/refresher/test_certificate_refresher.py --- old/python-proton-vpn-api-core-0.42.3/tests/core/refresher/test_certificate_refresher.py 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/tests/core/refresher/test_certificate_refresher.py 2025-04-07 13:04:16.000000000 +0200 @@ -22,6 +22,25 @@ from proton.vpn.core.refresher.certificate_refresher import CertificateRefresher, generate_backoff_value from proton.vpn.core.refresher.scheduler import RunAgain +from proton.session.exceptions import ProtonAPIError + + +@pytest.mark.asyncio +async def test_refresh_schedules_next_refresh_if_certificate_is_expired_and_api_error_exception_is_raised(): + session_holder = Mock() + session = session_holder.session + + refresher = CertificateRefresher(session_holder=session_holder) + + session.fetch_certificate = AsyncMock(side_effect=ProtonAPIError( + http_code=429, + http_headers={}, + json_data={"Code": 429, "Error": "Error message"} + )) + new_certificate = Mock() + new_certificate.remaining_time_to_next_refresh = 600 + + next_refresh_delay = await refresher.refresh() @pytest.mark.asyncio @@ -59,4 +78,4 @@ random_component=random_component ) - assert backoff == expected_backoff \ No newline at end of file + assert backoff == expected_backoff diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/tests/core/refresher/test_client_config_refresher.py new/python-proton-vpn-api-core-0.42.4/tests/core/refresher/test_client_config_refresher.py --- old/python-proton-vpn-api-core-0.42.3/tests/core/refresher/test_client_config_refresher.py 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/tests/core/refresher/test_client_config_refresher.py 2025-04-07 13:04:16.000000000 +0200 @@ -22,6 +22,7 @@ from proton.vpn.core.refresher.client_config_refresher import ClientConfigRefresher from proton.vpn.core.refresher.scheduler import RunAgain +from proton.session.exceptions import ProtonAPIError @pytest.mark.asyncio @@ -40,3 +41,20 @@ session.fetch_client_config.assert_called_once() assert next_refresh_delay == RunAgain.after_seconds(new_client_config.seconds_until_expiration) + + +@pytest.mark.asyncio +async def test_refresh_schedules_next_refresh_if_client_config_is_expired_and_api_error_exception_is_raised(): + session_holder = Mock() + session = session_holder.session + refresher = ClientConfigRefresher(session_holder=session_holder) + + new_client_config = Mock() + new_client_config.seconds_until_expiration = 60 + session.fetch_client_config = AsyncMock(side_effect=ProtonAPIError( + http_code=429, + http_headers={}, + json_data={"Code": 429, "Error": "Error message"} + )) + + next_refresh_delay = await refresher.refresh() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/tests/core/refresher/test_feature_flags_refresher.py new/python-proton-vpn-api-core-0.42.4/tests/core/refresher/test_feature_flags_refresher.py --- old/python-proton-vpn-api-core-0.42.3/tests/core/refresher/test_feature_flags_refresher.py 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/tests/core/refresher/test_feature_flags_refresher.py 2025-04-07 13:04:16.000000000 +0200 @@ -22,6 +22,7 @@ from proton.vpn.core.refresher.feature_flags_refresher import FeatureFlagsRefresher from proton.vpn.core.refresher.scheduler import RunAgain +from proton.session.exceptions import ProtonAPIError @pytest.mark.asyncio @@ -41,3 +42,21 @@ session.fetch_feature_flags.assert_called_once() assert next_refresh_delay == RunAgain.after_seconds(new_feature_flags.seconds_until_expiration) + + +@pytest.mark.asyncio +async def test_refresh_schedules_next_refresh_if_feature_flags_is_expired_and_api_error_exception_is_raised(): + session_holder = Mock() + session = session_holder.session + + refresher = FeatureFlagsRefresher(session_holder=session_holder) + + new_feature_flags = Mock() + new_feature_flags.seconds_until_expiration = 60 + session.fetch_feature_flags = AsyncMock(side_effect=ProtonAPIError( + http_code=429, + http_headers={}, + json_data={"Code": 429, "Error": "Error message"} + )) + + next_refresh_delay = await refresher.refresh() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/tests/core/refresher/test_server_list_refresher.py new/python-proton-vpn-api-core-0.42.4/tests/core/refresher/test_server_list_refresher.py --- old/python-proton-vpn-api-core-0.42.3/tests/core/refresher/test_server_list_refresher.py 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/tests/core/refresher/test_server_list_refresher.py 2025-04-07 13:04:16.000000000 +0200 @@ -16,13 +16,16 @@ You should have received a copy of the GNU General Public License along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>. """ -from unittest.mock import Mock, AsyncMock +from unittest.mock import Mock, AsyncMock, patch import pytest from proton.vpn.core.refresher.scheduler import RunAgain from proton.vpn.core.refresher.server_list_refresher import ServerListRefresher +from proton.session.exceptions import ProtonAPIError + + @pytest.mark.asyncio async def test_refresh_fetches_server_list_if_expired_and_returns_next_refresh_delay(): @@ -107,3 +110,28 @@ # And the next refresh should've been scheduled when the current # server list expires. assert next_refresh_delay == RunAgain.after_seconds(session.server_list.seconds_until_expiration) + + +@pytest.mark.asyncio +@patch("proton.vpn.core.refresher.server_list_refresher.ServerList.get_loads_refresh_interval_in_seconds") +async def test_refresh_schedules_next_refresh_if_server_list_is_expired_and_api_error_exception_is_raised(mock_get_loads_refresh_interval_in_seconds): + get_loads_refresh_interval_in_seconds = 10 + mock_get_loads_refresh_interval_in_seconds.return_value = get_loads_refresh_interval_in_seconds + session_holder = Mock() + session = session_holder.session + + # The current server list is expired. + session.server_list.expired = True + + # Mock a 429 response from the server + session.fetch_server_list = AsyncMock(side_effect=ProtonAPIError( + http_code=429, + http_headers={}, + json_data={"Code": 429, "Error": "Error message"} + )) + + refresher = ServerListRefresher(session_holder=session_holder) + refresher.server_list_updated_callback = Mock() + + # No error message should be raised since it's been swallowed + next_refresh_delay = await refresher.refresh() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.42.3/versions.yml new/python-proton-vpn-api-core-0.42.4/versions.yml --- old/python-proton-vpn-api-core-0.42.3/versions.yml 2025-02-24 15:38:38.000000000 +0100 +++ new/python-proton-vpn-api-core-0.42.4/versions.yml 2025-04-07 13:04:16.000000000 +0200 @@ -1,3 +1,12 @@ +version: 0.42.4 +time: 2025/04/04 13:00 +author: Alexandru Cheltuitor +email: alexandru.cheltui...@proton.ch +urgency: low +stability: unstable +description: +- Swallow and re-schedule server refresh when we receive HTTP code 429. +--- version: 0.42.3 time: 2025/02/24 12:00 author: Alexandru Cheltuitor