From 24ad6e70727b357e74a0d4529729461f2bcac1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alfredo=20Pe=C3=B1a?= <15361812+arpena@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:08:48 -0300 Subject: [PATCH] Fix InvalidSessionException treated as permanent auth failure Two changes: 1. Change InvalidSessionException to inherit from BaseSagemcomException instead of UnauthorizedException. An invalid session is a transient error (e.g., router invalidated the session due to a concurrent login), not a permanent authorization failure. Consumers that catch UnauthorizedException to signal permanent auth failures (like Home Assistant's ConfigEntryAuthFailed) should not catch transient session errors. 2. Increase max_tries from 1 to 2 on all backoff-decorated API methods. With max_tries=1, the on_backoff=retry_login handler fires but no retry actually occurs. With max_tries=2, after an InvalidSessionException the client will re-login and retry the operation once before giving up. --- sagemcom_api/client.py | 12 ++++++------ sagemcom_api/exceptions.py | 9 +++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/sagemcom_api/client.py b/sagemcom_api/client.py index 920d5da..e2c6081 100644 --- a/sagemcom_api/client.py +++ b/sagemcom_api/client.py @@ -403,7 +403,7 @@ async def get_value_by_xpath(self, xpath: str, options: dict | None = None) -> d LoginTimeoutException, InvalidSessionException, ), - max_tries=1, + max_tries=2, on_backoff=retry_login, ) async def get_values_by_xpaths(self, xpaths: dict[str, str], options: dict | None = None) -> dict: @@ -445,7 +445,7 @@ async def set_value_by_xpath(self, xpath: str, value: str, options: dict | None LoginTimeoutException, InvalidSessionException, ), - max_tries=1, + max_tries=2, on_backoff=retry_login, ) async def set_values_by_xpaths(self, xpaths: dict[str, str], options: dict | None = None) -> dict: @@ -476,7 +476,7 @@ async def set_values_by_xpaths(self, xpaths: dict[str, str], options: dict | Non LoginTimeoutException, InvalidSessionException, ), - max_tries=1, + max_tries=2, on_backoff=retry_login, ) async def get_device_info(self) -> DeviceInfo: @@ -507,7 +507,7 @@ async def get_device_info(self) -> DeviceInfo: LoginTimeoutException, InvalidSessionException, ), - max_tries=1, + max_tries=2, on_backoff=retry_login, ) async def get_hosts(self, only_active: bool | None = False) -> list[Device]: @@ -529,7 +529,7 @@ async def get_hosts(self, only_active: bool | None = False) -> list[Device]: LoginTimeoutException, InvalidSessionException, ), - max_tries=1, + max_tries=2, on_backoff=retry_login, ) async def get_port_mappings(self) -> list[PortMapping]: @@ -547,7 +547,7 @@ async def get_port_mappings(self) -> list[PortMapping]: LoginTimeoutException, InvalidSessionException, ), - max_tries=1, + max_tries=2, on_backoff=retry_login, ) async def get_logs(self) -> str: diff --git a/sagemcom_api/exceptions.py b/sagemcom_api/exceptions.py index 748d1ae..0617391 100644 --- a/sagemcom_api/exceptions.py +++ b/sagemcom_api/exceptions.py @@ -31,8 +31,13 @@ class AuthenticationException(UnauthorizedException): """Raised when authentication is not correct.""" -class InvalidSessionException(UnauthorizedException): - """Raised when session is invalid.""" +class InvalidSessionException(BaseSagemcomException): + """Raised when session is invalid. + + This is a transient error that occurs when the router invalidates the + session (e.g., due to a concurrent login). It should not be treated as + a permanent authorization failure. + """ class LoginRetryErrorException(BaseSagemcomException):