fix: make TokenManager.is_valid a property, matching the async mirror - #8
Conversation
TokenManager.is_valid was a method while AsyncTokenManager.is_valid was a
property, so porting sync code to async raised
`TypeError: 'bool' object is not callable` at runtime. Neither suite caught
it because each only exercised its own side.
Unified on the property form rather than the method form because:
- access_token and token are already properties on BOTH managers, so
is_valid was the odd one out within its own class.
- A zero-arg, side-effect-free boolean accessor reads better as a property.
- Nothing is published to PyPI yet, so there are no external callers to
break. That cost only grows after the first release.
is_valid is called nowhere in src/ -- neither client uses it -- so the blast
radius is four assertions in tests/unit/test_auth.py.
Adds test_managers_expose_the_same_public_surface, which compares both
classes' public members AND their kinds (property vs function) via
inspect.getattr_static, so future drift fails the build instead of being
found by a user. `aclose` is the single sanctioned async-only member.
Verified to catch the original bug: reverting auth.py makes it fail with
{'is_valid': ('function', 'property')}.
Also recorded in AGENTS.md while verifying the surfaces, not fixed here:
TokenManager creates its own httpx.Client when none is injected but exposes
no close(), whereas AsyncTokenManager tracks _owns_client and has aclose().
Not a live leak -- both clients inject their own httpx client, so the manager
never builds one on the normal path; it only affects a bare TokenManager(),
which is not exported.
303 tests. ruff, ruff format, mypy src/myinvois all clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughChangesThe authentication API now exposes Authentication API parity
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/unit/test_auth.py (1)
111-118: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMake the refresh-margin test acquire a token.
This assertion runs before
mgr.get_token(), sois_validis false simply because no token exists. The test would still pass if the refresh-margin logic were deleted. Mock two responses, callget_token()twice, and assert that the route is called twice.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/test_auth.py` around lines 111 - 118, Update test_token_manager_refresh_margin to mock two token endpoint responses, call mgr.get_token() twice, and assert the mocked route was called twice. Ensure the test validates refresh-margin behavior after an initial token is acquired rather than checking is_valid before any token exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@tests/unit/test_auth.py`:
- Around line 111-118: Update test_token_manager_refresh_margin to mock two
token endpoint responses, call mgr.get_token() twice, and assert the mocked
route was called twice. Ensure the test validates refresh-margin behavior after
an initial token is acquired rather than checking is_valid before any token
exists.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3167de9d-bae8-435a-8eda-9e4b19ce6b3b
📒 Files selected for processing (4)
AGENTS.mdsrc/myinvois/auth.pytests/unit/test_async_auth.pytests/unit/test_auth.py
One-line source change plus a regression guard.
The bug
TokenManager.is_validwas a method whileAsyncTokenManager.is_validwas a property, so porting sync code to async raisedTypeError: 'bool' object is not callableat runtime. Neither suite caught it because each only exercised its own side.Why the property form, not the method form
access_tokenandtokenare already properties on both managers, sois_validwas the odd one out within its own class.is_validis called nowhere insrc/(neither client uses it), so the blast radius is four assertions intests/unit/test_auth.py.Regression guard
A one-line fix doesn't stop the drift recurring, so this adds
test_managers_expose_the_same_public_surface. It compares both classes' public members and their kinds (propertyvsfunction) viainspect.getattr_static, withacloseas the single sanctioned async-only member. Adding a member to one manager and not the other now fails the build.Verified to catch the original bug — reverting
auth.pymakes it fail with:Found while verifying, not fixed here
TokenManagercreates its ownhttpx.Clientwhen none is injected but exposes noclose(), whereasAsyncTokenManagertracks_owns_clientand hasaclose().I checked before calling it a leak — it isn't one. Both clients inject their own
httpxclient, so the manager never builds one on the normal path. It only affects a bareTokenManager(), which isn't exported. Recorded in AGENTS.md as low priority.Testing
303 tests.
ruff check,ruff format --check,mypy src/myinvoisall clean.Next
A follow-up PR will fix
test_auth.py::test_token_manager_refresh_margin, which is vacuous — it never callsget_token(), sois_validis False only because no token was ever acquired. It passes withexpires_in=3600(far outside the 60s margin) and 0 calls to the token endpoint, i.e. it would pass with the refresh-margin logic deleted. Kept separate to kept this PR single-concern; it touches the same file so it goes after this merges.🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
Bug Fixes
TokenManager.is_validto a boolean property for consistent access across authentication managers.Tests