feat(teslemetry): add typed authorized clients accessor#76
Merged
Conversation
added 2 commits
July 13, 2026 07:26
Adds a typed dataclass accessor over TeslemetryEnergySite's undocumented list_authorized_clients() response so API-response parsing lives in the library instead of being reimplemented in the Home Assistant integration (HA core PR #176328). Field access checks key presence rather than truthiness so a legal falsy value (e.g. authorized_client_type=0) isn't mistaken for a missing field, and an explicitly present but empty authorized_clients list is returned as [] (authoritative: zero clients) rather than collapsed into the same None used for a genuinely unknown (absent field / null body) outcome.
Bre77
commented
Jul 12, 2026
| f"api/1/energy_sites/{self.energy_site_id}/command/authorized_clients", | ||
| ) | ||
|
|
||
| async def get_authorized_clients(self) -> AuthorizedClients: |
Member
Author
There was a problem hiding this comment.
I think this should be called find_authorized_clients
added 4 commits
July 13, 2026 07:52
…hape Narrows the typed accessor to what's actually confirmed: a precise, non-recursive envelope unwrap (no multi-key/depth-first search) instead of mirroring the HA config flow's speculative _iter_clients fallback search, and the AuthorizedClient model trimmed to only public_key/state - the two fields a pairing flow reads - since no site has a populated client list yet to confirm any other field names against. state is now typed via AuthorizedClientState (const.py, the schema of record - Tesla has not published this endpoint's schema), accepting both int and string wire forms; a present-but-unrecognized value passes through raw rather than being dropped to None. A None body, an unrecognized response shape, and an explicit empty list all collapse to a typed empty list (AuthorizedClients.clients is now always a list, never None) - this endpoint has no evidence yet that those cases mean anything different from each other.
…essor The auto-generated docs section described the pre-amendment API (clients is None for unknown vs [] for empty, a description field). Updates it to match: clients is always a list, and only public_key/state are modeled.
…d_clients Per PR review: this isn't a straight getter, it normalizes/parses the raw response into the typed model, so it should use the find_ prefix already established for lookup/discovery methods in this library (find_server, find_vehicle).
This was referenced Jul 12, 2026
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.
Intent
Two rounds of amendment on top of the original typed-accessor PR, per upstream-consumer (captain) review feedback:
Round 1 - tighten to evidenced wire shape: the original commit's accessor mirrored HA's config_flow.py _iter_clients with a broad recursive/depth-first wrapper-key search and a 3-way None/absent/empty distinction. Replaced with a precise, single-path envelope unwrap of only the confirmed real shape ({"response": {"authorized_clients": [...]}}, or a bare list with no envelope) - no recursive search - because the site used for validation currently has zero paired clients, so the populated multi-wrapper-key form _iter_clients defends against cannot be sampled to confirm, and building broad normalization against unconfirmed shapes was ruled out. AuthorizedClient is trimmed to only public_key and state - the two fields the pairing flow actually reads - dropping unevidenced description/key_type/authorized_client_type fields; each accepts only the specific key-name variant pairs _iter_clients/_is_verified actually check (public_key/publicKey, state/authorized_client_state). state is typed via AuthorizedClientState (const.py - the schema of record, since Tesla has not published an OpenAPI schema for this pairing endpoint), normalizing both int and string wire forms; a present-but-unrecognized value passes through raw rather than being dropped to None. A None response body, an unrecognized response shape, and an explicit empty authorized_clients list are no longer distinguished - all three collapse to a typed empty list (AuthorizedClients.clients is always a list, never None), since there is no current evidence those three inputs mean anything different from each other for this endpoint. This lets the HA integration fully delete its own _iter_clients helper and consume this accessor directly.
IMPORTANT for the PR body: note explicitly that the canonical wire form (the envelope shape and the public_key/state field names) is pinned from the consuming integration's own observed/defensive handling, not an official Tesla spec - Tesla has not published an OpenAPI schema for pairing endpoints - and that live-sample confirmation with an actual populated client list is a follow-up, not yet done.
Round 2 - rename per inline PR review comment (Bre77, on tesla_fleet_api/teslemetry/energysite.py line 181: "I think this should be called find_authorized_clients"): renamed get_authorized_clients to find_authorized_clients across the method, its docstring, AGENTS.md, docs/teslemetry.md, and all tests, since the method isn't a straight getter - it normalizes/parses the raw response into the typed model - and this library already uses a find_ prefix for exactly that kind of lookup/normalize method (find_server in tesla/fleet.py and teslemetry/teslemetry.py and tessie/tessie.py; find_vehicle in tesla/vehicle/bluetooth.py), so this matches existing naming convention rather than inventing a new one.
New tests added in round 1 cover: bare-list-no-envelope, camelCase/alternate-key-name entry fields, state-as-string normalization, a present-but-unrecognized state value surviving parsing (not coerced to None), explicit-empty-list, absent-field, and null-body - all producing the typed empty list.
What Changed
TeslemetryEnergySite.find_authorized_clients()with typed normalization for the evidenced authorized-clients response shapes, returningAuthorizedClients.clientsas a list ofAuthorizedCliententries with normalizedAuthorizedClientStatevalues where recognized.Risk Assessment
✅ Low: The change is well-bounded to a new typed Teslemetry accessor with conservative parsing semantics and focused coverage for the documented shapes; I found no material merge-blocking or follow-up issues in the changed code.
Testing
Ran the focused authorized-clients tests, generated an API-level transcript showing
find_authorized_clients()calling the Teslemetry endpoint and normalizing the intended wire shapes, then ran the full test suite; all exercised checks passed.Evidence: find_authorized_clients API transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
uv run pytest tests/test_teslemetry_authorized_clients.pyuv run python - <<'PY' > /tmp/no-mistakes-evidence/01KXC5AWPQB347GEW657J3FMYK/find_authorized_clients_transcript.json ...uv run pytest tests✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.