Skip to content

Allow injecting a requests.Session to keep logins from piling up - #162

Open
proscar87 wants to merge 2 commits into
indykoning:masterfrom
proscar87:feat/injectable-session
Open

proscar87 wants to merge 2 commits into
indykoning:masterfrom
proscar87:feat/injectable-session

Conversation

@proscar87

@proscar87 proscar87 commented Aug 7, 2026 •

Copy link
Copy Markdown

Follows up on the session-persistence idea from #160, which you asked for there.

Why

Every GrowattApi builds its own fresh requests.Session, so nothing is shared or persisted. As you described, in the HA integration login() 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 that requests also 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 GrowattApi on 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:

  • The hook runs on every response that goes through that session, including requests that have nothing to do with Growatt. A non-2xx answer from another service raises HTTPError from raise_for_status.
  • With Raise GrowattRateLimitError when a response body carries Growatt's 507 code #160 merged as well, the same hook also reads every response body, so a foreign HTTP 200 whose JSON happens to carry success: false, msg: "507" would raise GrowattRateLimitError. I checked this with both heads merged (no conflicts): one hook after three instances, and that body raises GrowattRateLimitError.
  • So the safe pattern is a session dedicated to Growatt, shared only between GrowattApi instances.

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). ruff 0.15.16 and mypy --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

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>
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.

1 participant