feat: basketball-api HTTP client with Keycloak auth #11
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "5-basketball-client"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Async httpx client that authenticates to basketball-api via Keycloak client credentials flow, caches tokens with 30s pre-expiry refresh, and provides typed functions for all 14 basketball-api endpoints (7 reads, 7 writes). Tenant slug hardcoded to "westside-kings-queens".
Changes
app/basketball.py-- newBasketballClientclass with:{KEYCLOAK_REALM_URL}/protocol/openid-connect/token)_auth_headers()get_dashboard,list_players,get_player,list_teams,get_roster,get_subscriptions_overview,list_subscriptionsupdate_player,assign_player_to_team,remove_player_from_team,toggle_player_visibility,create_team,checkin_player,bulk_assign_tryout_numbersBasketballAPIErrorexception with status code and bodytests/test_basketball.py-- 22 unit tests covering:Test Plan
Review Checklist
BasketballAPIErrorwith status + bodyapp/groupme.py(wiring is #6)Related Notes
Related
Closes #5
QA Review -- PR #11
Endpoint Verification (cross-referenced against issue #5 table)
Reads (7/7 match):
Writes (7/7 match):
Acceptance Criteria
{KEYCLOAK_REALM_URL}/protocol/openid-connect/tokenBasketballAPIError)TENANT_SLUGconstant)Code Quality
close()cleanup_request()Boundary Check
app/groupme.pyrequirements.txt(httpx already present from #4)app/basketball.py,tests/test_basketball.pyNits
None.
VERDICT: APPROVED
PR #11 Review
DOMAIN REVIEW
Tech stack: Python / async httpx / Keycloak OAuth2 client credentials / pytest-asyncio
Token management -- The 30s pre-expiry refresh logic in
_ensure_tokenis correct. The fallbackexpires_in=300onpayload.get("expires_in", 300)is a reasonable default. Token is cached on the instance and reused across requests. Each token acquisition creates a scopedasync with httpx.AsyncClient()that is properly closed.HTTP client lifecycle --
_get_http()lazily creates the API client;close()properly callsaclose()and resets toNone. Clean resource management.Error handling --
BasketballAPIErrorcarries bothstatus_codeandbody, and_requestcorrectly raises on any non-2xx status. 204/empty-content responses are handled by returningNone. Keycloak token errors are also raised asBasketballAPIError, which is a good unified error surface.14 endpoints -- All 7 reads and 7 writes are thin wrappers over
_requestwith the correct HTTP method and path. Tenant slug is centralized inTENANT_SLUGconstant and used consistently inget_roster,checkin_player, andbulk_assign_tryout_numbers.Test coverage -- 22 tests across 5 classes. Token lifecycle is well-tested (acquisition, caching, pre-expiry refresh, Keycloak error). Every endpoint is individually tested for correct HTTP method, path, and JSON body. Bearer header presence is verified. HTTP error propagation covers 500 and 404. This is solid coverage.
No secrets in code -- All credentials are sourced from
settings(viaapp.config) or constructor arguments. No hardcoded API keys, passwords, or tokens.BLOCKERS
None.
NITS
Concurrent token refresh race (
app/basketball.py:67-85): If two coroutines hit_ensure_tokenconcurrently when the token is expired, both will POST to Keycloak. Not a correctness bug (both get valid tokens), but wasteful. Anasyncio.Lockaround the refresh block would prevent duplicate requests. Low priority for a single-consumer bot.Type hints on write methods (
app/basketball.py:153,158,175):data: dictlacks key/value type annotation. PEP 484 would preferdict[str, Any]for clarity. The-> Anyreturn types on all methods are also quite broad -- consider defining response TypedDicts for the most-used endpoints as the codebase matures.Missing test for
close()behavior (tests/test_basketball.py): No test verifies thatclose()properly resets the internal HTTP client. Minor, since the fixture exercises it implicitly.Missing test for 204 on a read path (
tests/test_basketball.py): The 204/empty-content branch in_requestis only exercised viatest_remove_player_from_team(a write). No read test covers the edge case where a read endpoint returns 204. Low risk since the code path is shared, but worth a quick test for completeness._tokentype assertion (app/basketball.py:82):self._token = payload["access_token"]-- if the Keycloak response is missingaccess_token, this raises a rawKeyError. Consider catching this and raisingBasketballAPIErrorwith a descriptive message instead.SOP COMPLIANCE
5-basketball-clientmatches issue #5)Closes #5) and dependencies (#4, #1)feat: basketball-api HTTP client with Keycloak auth)PROCESS OBSERVATIONS
Clean, focused PR. Two new files, zero modifications to existing code. The 22 tests are well-structured and cover the full endpoint surface plus auth lifecycle. The separation between this client module (#5) and the wiring/integration (#6) shows good decomposition discipline. No DORA risk -- this is additive code with no deployment-time state changes.
VERDICT: APPROVED