From bda29a5784e30dacbb22e44da3874a76c935f1bc Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 20 May 2026 22:52:20 +0200 Subject: [PATCH 1/4] Address PR #1872 review feedback - Fix ActionQueueSettings validation messages to reference actual field names (delay, max_actions) instead of old prefixed names - Fix Rexel docs: getting-started.md incorrectly showed UsernamePasswordCredentials instead of RexelOAuthCodeCredentials - Mark Rexel as supported in docs/index.md (auth strategy exists) - Improve response_handler message parsing readability by splitting walrus assignment into explicit steps --- docs/getting-started.md | 12 ++++++++---- docs/index.md | 2 +- pyoverkiz/action_queue.py | 4 ++-- pyoverkiz/response_handler.py | 3 ++- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 6ec57f1b..0b61e30a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -152,21 +152,25 @@ Use a cloud server when you want to connect through the vendor’s public API. U === "Rexel (cloud)" - Authentication to the Rexel cloud requires your mobile app username and password. + Authentication to the Rexel cloud uses OAuth2 authorization code flow. + You need an authorization code and redirect URI obtained from the Rexel OAuth2 consent flow. - Use `Server.REXEL` with `UsernamePasswordCredentials` to authenticate. + Use `Server.REXEL` with `RexelOAuthCodeCredentials` to authenticate. ```python import asyncio - from pyoverkiz.auth.credentials import UsernamePasswordCredentials + from pyoverkiz.auth.credentials import RexelOAuthCodeCredentials from pyoverkiz.client import OverkizClient from pyoverkiz.enums import Server async def main() -> None: async with OverkizClient( server=Server.REXEL, - credentials=UsernamePasswordCredentials("you@example.com", "password"), + credentials=RexelOAuthCodeCredentials( + code="your-authorization-code", + redirect_uri="https://your-redirect-uri", + ), ) as client: await client.login() diff --git a/docs/index.md b/docs/index.md index 08cc7bab..d23de819 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,7 +33,7 @@ pyOverkiz is an async Python library for interacting with Overkiz-based platform - Somfy TaHoma - Somfy TaHoma Switch - Thermor Cozytouch +- Rexel Energeasy Connect - Brandt Smart Control **\*** -- Rexel Energeasy Connect **\*** \[*] _These servers utilize an authentication method that is currently not supported by this library._ diff --git a/pyoverkiz/action_queue.py b/pyoverkiz/action_queue.py index 8a124686..6c1c4188 100644 --- a/pyoverkiz/action_queue.py +++ b/pyoverkiz/action_queue.py @@ -24,10 +24,10 @@ class ActionQueueSettings: def validate(self) -> None: """Validate configuration values for the action queue.""" if self.delay <= 0: - raise ValueError(f"action_queue_delay must be positive, got {self.delay!r}") + raise ValueError(f"delay must be positive, got {self.delay!r}") if self.max_actions < 1: raise ValueError( - f"action_queue_max_actions must be at least 1, got {self.max_actions!r}" + f"max_actions must be at least 1, got {self.max_actions!r}" ) diff --git a/pyoverkiz/response_handler.py b/pyoverkiz/response_handler.py index 637755ed..5ab94b8a 100644 --- a/pyoverkiz/response_handler.py +++ b/pyoverkiz/response_handler.py @@ -147,7 +147,8 @@ async def check_response(response: ClientResponse) -> None: if error_code: # Error messages between cloud and local servers differ slightly in quoting and punctuation. # Normalise so substring matching works across both variants. - message = message.strip('".') if (message := result.get("error")) else "" + raw_message = result.get("error") + message = raw_message.strip('".') if raw_message else "" # 1. Primary dispatch: match on errorCode (+ optional message substring) for code, pattern, error_class in _ERROR_CODE_MESSAGE_MAP: From 81bcb790d7dc387e7153231895e7068011a22e61 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 20 May 2026 22:53:59 +0200 Subject: [PATCH 2/4] =?UTF-8?q?Comment=20out=20Rexel=20docs=20=E2=80=94=20?= =?UTF-8?q?OAuth2=20flow=20not=20fully=20working=20yet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/getting-started.md | 2 ++ docs/index.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 0b61e30a..bc1332aa 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -150,6 +150,7 @@ Use a cloud server when you want to connect through the vendor’s public API. U asyncio.run(main()) ``` + diff --git a/docs/index.md b/docs/index.md index d23de819..08cc7bab 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,7 +33,7 @@ pyOverkiz is an async Python library for interacting with Overkiz-based platform - Somfy TaHoma - Somfy TaHoma Switch - Thermor Cozytouch -- Rexel Energeasy Connect - Brandt Smart Control **\*** +- Rexel Energeasy Connect **\*** \[*] _These servers utilize an authentication method that is currently not supported by this library._ From 1534b3564af5908bc15a667491517cb3e66e0645 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 20 May 2026 22:56:09 +0200 Subject: [PATCH 3/4] Address tetienne's review feedback from PR #1872 - Remove Protocol inheritance from BaseAuthStrategy; structural subtyping satisfies the AuthStrategy protocol without explicit inheritance (fixes type system treating BaseAuthStrategy as a Protocol) - Convert OverkizClient constructor docstring from Sphinx :param: style to Google Args: style for project consistency --- pyoverkiz/auth/factory.py | 2 +- pyoverkiz/auth/strategies.py | 9 ++++++--- pyoverkiz/client.py | 11 ++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pyoverkiz/auth/factory.py b/pyoverkiz/auth/factory.py index 5d578f46..943cd8ef 100644 --- a/pyoverkiz/auth/factory.py +++ b/pyoverkiz/auth/factory.py @@ -13,8 +13,8 @@ TokenCredentials, UsernamePasswordCredentials, ) +from pyoverkiz.auth.base import AuthStrategy from pyoverkiz.auth.strategies import ( - AuthStrategy, BearerTokenAuthStrategy, CozytouchAuthStrategy, LocalTokenAuthStrategy, diff --git a/pyoverkiz/auth/strategies.py b/pyoverkiz/auth/strategies.py index c3b3cd3c..4c03d514 100644 --- a/pyoverkiz/auth/strategies.py +++ b/pyoverkiz/auth/strategies.py @@ -16,7 +16,7 @@ from aiohttp import ClientSession, FormData -from pyoverkiz.auth.base import AuthContext, AuthStrategy +from pyoverkiz.auth.base import AuthContext from pyoverkiz.auth.credentials import ( LocalTokenCredentials, RexelOAuthCodeCredentials, @@ -53,8 +53,11 @@ MIN_JWT_SEGMENTS = 2 -class BaseAuthStrategy(AuthStrategy): - """Base class for authentication strategies.""" +class BaseAuthStrategy: + """Base class for authentication strategies. + + Satisfies the AuthStrategy protocol via structural subtyping. + """ def __init__( self, diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index 060e5116..df964f8d 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -202,11 +202,12 @@ def __init__( ) -> None: """Constructor. - :param server: ServerConfig - :param credentials: Credentials for authentication - :param verify_ssl: Enable SSL certificate verification - :param session: optional ClientSession - :param settings: behavioral settings for the client (default None) + Args: + server: ServerConfig, Server enum, or server key string. + credentials: Credentials for authentication. + verify_ssl: Enable SSL certificate verification. + session: Optional ClientSession. + settings: Behavioral settings for the client. """ self.server_config = self._normalize_server(server) From 7d73525735a7510cad8187148153e458e82da836 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 20 May 2026 22:59:34 +0200 Subject: [PATCH 4/4] Revert BaseAuthStrategy Protocol inheritance removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inheriting from a Protocol does not make the subclass a Protocol — only direct inheritance from typing.Protocol does (PEP 544). The explicit inheritance clearly signals the contract BaseAuthStrategy implements and is correctly enforced by mypy. --- pyoverkiz/auth/factory.py | 2 +- pyoverkiz/auth/strategies.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pyoverkiz/auth/factory.py b/pyoverkiz/auth/factory.py index 943cd8ef..5d578f46 100644 --- a/pyoverkiz/auth/factory.py +++ b/pyoverkiz/auth/factory.py @@ -13,8 +13,8 @@ TokenCredentials, UsernamePasswordCredentials, ) -from pyoverkiz.auth.base import AuthStrategy from pyoverkiz.auth.strategies import ( + AuthStrategy, BearerTokenAuthStrategy, CozytouchAuthStrategy, LocalTokenAuthStrategy, diff --git a/pyoverkiz/auth/strategies.py b/pyoverkiz/auth/strategies.py index 4c03d514..c3b3cd3c 100644 --- a/pyoverkiz/auth/strategies.py +++ b/pyoverkiz/auth/strategies.py @@ -16,7 +16,7 @@ from aiohttp import ClientSession, FormData -from pyoverkiz.auth.base import AuthContext +from pyoverkiz.auth.base import AuthContext, AuthStrategy from pyoverkiz.auth.credentials import ( LocalTokenCredentials, RexelOAuthCodeCredentials, @@ -53,11 +53,8 @@ MIN_JWT_SEGMENTS = 2 -class BaseAuthStrategy: - """Base class for authentication strategies. - - Satisfies the AuthStrategy protocol via structural subtyping. - """ +class BaseAuthStrategy(AuthStrategy): + """Base class for authentication strategies.""" def __init__( self,