Repository navigation
Conversation
The test suite has failed on main since the lock moved to newer packages:
- PyJWT 2.15 rejects trailing characters after the signature, and the test
token loaded from auth_token.txt ends with the file's newline
('Invalid crypto padding'). The tests now strip it.
- aioresponses does not support aiohttp 3.14 yet (ClientResponse needs
stream_writer, pnuckowski/aioresponses#289). A uv constraint keeps the
development lock on aiohttp 3.13 until it does; the published requirement
(aiohttp>=3.0.0) is unchanged.
aioresponses was also listed as a runtime dependency although only the tests
use it, so every install pulled a test mocking library. It now sits in the
dev group.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…an auth error check_request_status() looked the status up in a dict that only knew 400, 401, 403 and 500, so any other status (429 when the daily quota is used up, 502/503 from the API, 404) raised KeyError from inside the library. - 429 raises the new TadoRateLimitError - any 5xx raises TadoServerError (defined but never used so far) - any other status raises TadoError instead of KeyError All of them subclass TadoError, so existing 'except TadoError' keeps working. A 400 from the token endpoint while refreshing (invalid_grant) raised TadoBadRequestError, so callers could not tell that the user has to log in again. _refresh_auth() now treats it like the login calls do and raises TadoAuthenticationError. This changes the exception type for that case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tado refresh tokens are single use. When the access token expired while several requests ran concurrently (Home Assistant updates several entities with asyncio.gather), each coroutine reached _refresh_auth(), sent the same refresh token and all but the first got HTTP 400, which the callers see as a failed authentication. The refresh now runs under an asyncio.Lock and the expiry is checked again once it is held, so waiting coroutines reuse the token the first one got. test_parallel_requests_refresh_token_once fails without the lock (5 of 6 refreshes rejected) and passes with it (1 refresh, 0 rejected). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tado rotates the refresh token on every refresh and the previous one stops working. A caller that keeps the token (Home Assistant stores it in its config entry) can only read Tado.refresh_token after its calls return; if a call fails after the rotation, or the process stops first, it keeps a token tado has already cancelled and the user has to log in again. Tado(..., token_update_callback=...) is called with every refresh token tado issues: the device login, login() and each refresh. A failing callback is logged and never breaks authentication. Without the argument nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed Changes
Stacked on PR A (the test-environment repair), so its first commit is PR A's. Please review the three commits after it; once PR A is merged, this PR shows only those.
TadoRateLimitError; any 5xx →TadoServerError(defined but never raised so far); any other status →TadoError, neverKeyError. All subclassTadoError, soexcept TadoErrorkeeps working._refresh_auth()now passeslogin=True, so a 400invalid_grantraisesTadoAuthenticationError. This changes the exception type for that one case (it wasTadoBadRequestError);test_refresh_auth_client_response_erroris updated accordingly._refresh_auth()takes anasyncio.Lockand checks the expiry again once it holds it, so concurrent callers reuse the token the first one got.test_parallel_requests_refresh_token_onceruns 6 concurrent refreshes against a token endpoint that refuses a used token: without the lock 5 are rejected, with it 1 refresh and 0 rejected.token_update_callback.Tado(..., token_update_callback=...)is called with every refresh token tado issues (device login,login(), each refresh). A failing callback is logged and never breaks auth. Nothing changes when it isn't passed.Tests:
test_check_request_status_maps_every_status(9 cases),test_parallel_requests_refresh_token_once,test_token_update_callback_gets_every_new_token,test_token_update_callback_error_does_not_break_auth. 95 passed, coverage 96.7%, ruff, ruff format, mypy and pylint (10.00) clean.The same fixes went into PyTado (all merged, released with 0.19.4): wmalgadey/PyTado#367, wmalgadey/PyTado#368, wmalgadey/PyTado#369.
Related Issues