Repository navigation
Allow injecting a requests.Session to keep logins from piling up - #162
Conversation
Requested by @johanzander in indykoning#160: every GrowattApi instance builds its own fresh Session, so nothing is shared or persisted. In the Home Assistant integration login() is called from the config flow, from setup, and from the coordinator on every update cycle -- which is the login frequency that drives accounts into the ~24h 507 lockout that indykoning#160 only reports. A constructor-injected `session` lets a consumer share one Session across API instances *and* restore persisted cookies at startup. The library takes on no storage responsibility: where the cookies live stays the caller's business. The raise_for_status hook is now appended rather than assigned. A shared Session may already carry hooks and replacing `session.hooks` outright would drop them silently; a bare callable is normalised to a list, which requests permits. Six of the seven tests fail without the parameter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every GrowattApi appended its own raise_for_status hook, so N instances sharing one injected session left N hooks on it and each response was checked N times. Skip the install when an earlier instance already put the hook there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
johanzander
left a comment
There was a problem hiding this comment.
Thanks, sharing a session and restoring cookies is a good way to reduce re-logins and 507 lockouts. Appending to existing hooks rather than replacing them is the right call, and the tests cover it well.
Main point: headers.update overwrites the User-Agent on an injected session even when no agent_identifier was passed, so a caller's own User-Agent is silently replaced. With several instances on one session, the last one wins. Consider only setting it when the caller passed an agent_identifier.
Minor:
- De-duplicating by
__qualname__would also match any unrelated hook with the same qualname. A module-level hook function or a marker attribute, compared by identity, would be sturdier. - The hook doesn't catch the 507 lockout, which comes back as HTTP 200 with
msg: "507"in the body. That's fine, but the docstring could say that skippinglogin()while the persisted cookies are still valid is up to the caller, and that the session now raises on HTTP errors for any request.
…entity Review feedback on indykoning#162: - The User-Agent of an injected session is only changed when the caller passes agent_identifier or add_random_user_id. Before, every instance overwrote it, and with several instances on one session the last won. - The response hook is a module-level function and is matched by identity, so an unrelated hook with the same name is not mistaken for ours. - The docstring says that skipping login() while restored cookies are valid is up to the caller, that the session now raises on HTTP errors for every request, and that the 507 lockout (HTTP 200, msg 507) is not caught by that hook. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks @johanzander, all three addressed in 5bb15b8:
Four new tests; each fails against the change it guards. |
indykoning#160 put the 507 check inside the response hook that this branch moved to module level. The module-level _raise_for_status now calls _raise_if_rate_limited before raise_for_status, so an injected session gets it too, and a test holds that. The docstring said the hook did not catch the 507 lockout, which indykoning#160 made false; it now says it does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Merged master to resolve the conflict with #160 (9d31cfb). Since #160 put the 507 check in the response hook, the module-level |
|
@indykoning - want to have a look before merging? |
indykoning
left a comment
There was a problem hiding this comment.
Looks good and i appreciate the addition of the tests!
Follows up on the session-persistence idea from #160, which you asked for there.
Why
Every
GrowattApibuilds its own freshrequests.Session, so nothing is shared or persisted. As you described, in the HA integrationlogin()is called from the config flow, from setup, and from the coordinator on every update cycle — and that login frequency is what drives accounts into the ~24h lockout. #160 only reports the lockout; this is the part that can prevent it.The change
A constructor-injected
session: requests.Session | None = None, the shape you preferred. It lets a consumer share one session across API instances and restore persisted cookies at startup. The library stores nothing itself — where the cookies live stays the caller's business.Default behaviour is unchanged: with no argument it creates its own session exactly as before.
One thing worth reviewing
self.session.hooks = {"response": [_raise_for_status]}assigned the hooks dict, which is fine for a session this library owns but destructive for one it was handed — a caller sharing a session across libraries would have their own hooks silently dropped. It now appends, and normalises the bare-callable form thatrequestsalso permits.If you'd rather this library not touch a caller-supplied session's hooks at all, say so and I'll gate it — but then a shared session loses
raise_for_status, which the rest of the code relies on, so it would need a different error path.The hook is installed once per session: a second
GrowattApion the same session sees the first one's hook and doesn't add another. The first version of this PR appended one per instance, so three instances left three hooks and every response was checked three times.What a caller should know before sharing a session:
HTTPErrorfromraise_for_status.success: false, msg: "507"would raiseGrowattRateLimitError. I checked this with both heads merged (no conflicts): one hook after three instances, and that body raisesGrowattRateLimitError.GrowattApiinstances.Validation
Nine tests covering: default session still created, injected session used as-is, two instances sharing one, restored cookies surviving, pre-existing hooks kept, the hook installed once when three instances share a session, each response checked once with three instances, bare-callable hooks normalised, and the user-agent applied to the injected session. Eight of the nine fail on
master(without the parameter). The two hook-count tests also fail on this PR's first commit (assert 4 == 2,assert 3 == 1). Full suite: 10 passed (master: 1).ruff0.15.16 andmypy --ignore-missing-imports growattServer/are clean, as in CI. Merged with #160's current head: 20 passed, ruff and mypy clean.Not validated: I did not test against Growatt's server, so I never checked that restored cookies are actually accepted there, or for how long. The tests only show that the cookies stay on the injected session; whether Growatt honours them in place of a fresh
login()is unmeasured.🤖 Generated with Claude Code