Conversation
The provider session is established once, in `async_setup_entry`, and is
never renewed while the entry keeps running. `pycityvisitorparking` does
retry a request once after re-authenticating, but only when the provider
answers with an explicit auth status. A portal that reports an expired
session as a generic server error instead (DVSPortal answers
`POST /login/getbase` with `500 {"Message":"An error has occurred."}`)
never reaches that path, so the dead session stays in place and every
following update fails until the user reloads the entry by hand.
Reloading only helps because it logs in again, so do that automatically:
- Move the login out of `async_setup_entry` into `auth.py`, so setup and
runtime recovery establish a session the exact same way, including the
persisting and clearing of provider-resolved login params.
- Let the coordinator re-authenticate once and retry the fetch when an
update fails with an auth or provider error. Errors a new session
cannot fix are re-raised untouched: a `NetworkError` never reached the
provider, and rate-limit or maintenance responses would only be made
worse by adding a login. Attempts are spaced by `RELOGIN_COOLDOWN` so a
provider outage cannot turn every poll into a login.
- A re-login that is itself rejected still raises `ConfigEntryAuthFailed`,
so genuinely wrong credentials keep triggering the reauth flow.
Also derive entity availability from the coordinator. These entities
extend `BaseCoordinatorEntity`, which — unlike `CoordinatorEntity` — has
no `available` property, so the sensors kept reporting their last
successful values while every update failed. That is what made the
failure silent: no unavailable entities, no reauth notification, just
values frozen at the moment the session expired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rNpb7SZNGUMtU71kkPcBb
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.
Description
Problem
The provider session is established once, in
async_setup_entry, and is never renewed while the entry keeps running.pycityvisitorparkingdoes retry a request once after re-authenticating, but only when the provider answers with an explicit auth status. A portal that reports an expired session as a generic server error instead never reaches that path, so the dead session stays in place and every following update fails until the user reloads the entry by hand.Relates to #198. That issue and sir-Unknown/pyCityVisitorParking#93 address the DVSPortal-specific status mapping; this PR addresses the integration-side half — the coordinator's inability to recover from any stale session, whatever the provider returns.
Confirmed on Leiden (
/DVSWebAPI/api), which answersPOST /login/getbasewith500andapplication/json{"Message":"An error has occurred."}— a shape #93 deliberately leaves asProviderError. From my instance:status=500 operation=fetch_alllogged 8227 times since June, coordinator data frozen for three weeks, config entry still reportingloadedthroughout. Details in my comment on #198.Change
auth.py(new) — the login moves out ofasync_setup_entry, so setup and runtime recovery establish a session identically, including persisting and clearingresolved_login_params.AuthErrororProviderError. Errors a new session cannot fix are re-raised untouched: aNetworkErrornever reached the provider, andRateLimitError/ServiceUnavailableErrorwould only be made worse by adding a login. Attempts are spaced byRELOGIN_COOLDOWN(15 min) so a provider outage cannot turn every poll into a login.ConfigEntryAuthFailed, so genuinely wrong credentials keep triggering the reauth flow (AGENTS.md §11, §13).availablenow followscoordinator.last_update_success. These entities extendBaseCoordinatorEntity, which unlikeCoordinatorEntitydefines noavailable, so the sensors kept reporting their last successful values while every update failed. That is what made the failure silent: no unavailable entities, no reauth notification, just values frozen at the moment the session expired.No provider-specific branching is introduced, per AGENTS.md §7.
Tests
Eight cases added:
ProviderErrorrecovers via one re-login and a retried fetchAuthErrorlikewise recoversConfigEntryAuthFailedRateLimitError,NetworkErrorand a credential-less entry attempt no login at allFive of the eight fail on the parent commit. Full suite: 157 passed.
Not in scope
The service handlers (
start_reservationetc.) call the provider directly and would still fail on a dead session until the next poll heals it. Happy to follow up if you want those routed through the same recovery.Type of change
Checklist
ruff checkandruff formatpass (alsomypy: clean)hassfestpasses — not run locally, relying on CIyarn build) if frontend changed — n/a, no frontend changesCHANGELOG.mdupdated (for user-facing changes) — n/a, noCHANGELOG.mdin this repo🤖 Generated with Claude Code