Skip to content

Add token manager - #452

Merged
Danielhiversen merged 5 commits into
Danielhiversen:masterfrom
MartinHjelmare:add-token-manager
Aug 25, 2026
Merged

Add token manager#452
Danielhiversen merged 5 commits into
Danielhiversen:masterfrom
MartinHjelmare:add-token-manager

Conversation

@MartinHjelmare

@MartinHjelmare MartinHjelmare commented Aug 17, 2026

Copy link
Copy Markdown
Contributor
  • Consolidate the token reference and refresh into a class.

  • The goal is to avoid having multiple token references when using this library in an application. The existing multiple references are allowing the different references to drift apart. Also make the token refresh code simpler, and also the realtime connection less likely to encounter an expired token.

  • I need to test the code live for some days, before it could be merged.

closes #448


task_a.cancel()
with pytest.raises(asyncio.CancelledError):
await task_a

@MartinHjelmare MartinHjelmare Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review is incorrect.

Comment thread tibber/realtime.py Outdated
Comment thread tibber/realtime.py Outdated
@MartinHjelmare

Copy link
Copy Markdown
Contributor Author

I've tested this overnight ok.

@MartinHjelmare
MartinHjelmare marked this pull request as ready for review August 19, 2026 08:09
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
@augmentcode

augmentcode Bot commented Aug 19, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary:
This PR centralizes Tibber API token state in a shared TokenManager.
Changes:

  • Adds a manager that stores tokens, invokes optional async refresh callbacks, and coalesces concurrent refreshes.
  • Routes GraphQL, Data API, user-info, and realtime clients through the shared manager.
  • Refreshes the token before outbound HTTP requests and before each websocket handshake.
  • Overrides the gql websocket initialization hook so internal reconnects obtain a current token.
  • Deprecates Tibber.set_access_token and reconnects active realtime sessions after updates.
  • Updates fixtures and adds coverage for refresh coalescing, cancellation, and websocket initialization.
Technical notes: Refresh callback failures retain the last known token, while gql continues to manage websocket retries.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread tibber/data_api.py
access_token: str,
token_manager: TokenManager,
timeout: int = DEFAULT_TIMEOUT,
websession: aiohttp.ClientSession | None = None,

@augmentcode augmentcode Bot Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tibber/data_api.py:41 This replaces the publicly exported TibberDataAPI(access_token=...) constructor contract with token_manager: positional callers now pass a str that later fails at async_get_access_token, while keyword callers fail at construction. Other locations where this applies: tibber/realtime.py:35.

Severity: medium

Other Locations
  • tibber/realtime.py:35

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the TibberDataAPI is meant to be instantiated on its own. But if we want to make it backwards compatible, I can address this.

Comment thread tibber/token_manager.py Outdated
return self._access_token

def set_access_token(self, access_token: str) -> None:
"""Update the stored access token synchronously."""

@augmentcode augmentcode Bot Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tibber/token_manager.py:47 An in-flight async_get_access_token() refresh can complete after set_access_token() and overwrite the manually supplied value, so later HTTP/RT calls silently revert to a stale bearer token. This also affects the still-supported deprecated Tibber.set_access_token() path, which calls this setter.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The race is only reachable when a refresh_access_token callback is configured and the caller also pushes tokens via set_access_token — which is contradictory usage. Looking at async_get_access_token:

if self._refresh_access_token is None:
    return self._access_token  # no task, no race

Without a callback there is never an in-flight refresh task, so set_access_token is fully deterministic. With a callback, the callback is the single source of truth by design — a completing refresh writing the callback's token is the intended outcome, not a stale revert. I've added docstring notes to TokenManager.set_access_token, Tibber.set_access_token, and TibberDataAPI.set_access_token making this contract explicit.

@Danielhiversen Danielhiversen left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great 👍

@Danielhiversen
Danielhiversen merged commit e533625 into Danielhiversen:master Aug 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants