feat: add return type annotations to async DB functions (#20) #47
No reviewers
Labels
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cal/paper-dynasty-card-creation#47
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "ai/paper-dynasty-card-creation-20"
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?
Closes #20
Summary
Adds return type annotations to all six async functions in
db_calls.py, enabling static analysis to catch callers that don't handleNonereturns.db_get→Optional[dict](can returnNonewhennone_okay=True)url_get→aiohttp.ClientResponsedb_patch→dictdb_post→dictdb_put→dictdb_delete→dictAlso adds
Optionalto thefrom typing importline.Files Changed
db_calls.pyNotes
The linter auto-reformatted some f-string quote styles on save (double → single for nested strings). These are cosmetic-only and don't affect behavior.
Other observations
timeoutparameter is accepted by all async functions but never forwarded toaiohttp— it's effectively a no-op. Not fixed here per issue scope.int = None/dict = Nonewhich are technically incorrect (should beOptional[int], etc.) — pre-existing, out of scope for this issue.AI Code Review
Files Reviewed
db_calls.py(modified)Findings
Correctness
db_get → Optional[dict]: Correct. The function returnsNonewhennone_okay=Trueand the response is non-200, adicton success, or raisesValueError.Optional[dict]accurately captures this.url_get → aiohttp.ClientResponse: Correct as a type annotation. Worth noting for callers:return rexecutes insideasync with session.get(url) as r:, which closes the response and its underlying connection on return. The body will have already been read (it hasn't been, actually, in this function), so the returnedClientResponsewill have a closed connection. This is a pre-existing design issue — not introduced by this PR — and the annotation is accurate for what's actually returned.db_patch / db_post / db_put / db_delete → dict: Correct. These either returnjs(the parsed JSON response, expected to be a dict for this API) or raiseValueError. They never returnNone.Optionaladded to thefrom typing importline cleanly.Security
Style & Conventions
db_deletestill hastimeout=3without a type annotation (pre-existing, noted in PR body as out of scope). Consistent with the PR's declared scope.Suggestions
r.json()in aiohttp can return any JSON-deserializable type (dict,list,str,int). If the API ever returns a top-level array, the-> dictannotations ondb_patch/post/put/deletewould be technically imprecise. For this project's API the dict assumption holds, butAnyor a typedTypedDictwould be more correct long-term. Not a blocker.Verdict: COMMENT (LGTM)
Clean, focused change. The annotations accurately reflect the existing function contracts and
Optionalis the right choice fordb_get. The f-string reformatting is noise-free. Ready to merge.Automated review by Claude PR Reviewer
AI Code Review
Files Reviewed
db_calls.py(modified)Findings
Correctness
url_getreturn type is inaccurate —aiohttp.ClientResponseis a closed context manager here.The annotation
-> aiohttp.ClientResponseat line 79 is misleading and technically incorrect.url_getusesasync with session.get(url) as r:and then doesreturn rinside that context. Once theasync withblock exits, theClientResponseis closed and its connection released back to the pool. Any caller that receives the returned object and tries to read the body (e.g.await response.read()orawait response.json()) will get an error because the underlying connection is already closed.The correct fix for the type annotation would be to return
bytesor a parsed type (having awaitedr.read()before the context exits), notaiohttp.ClientResponse. As written, the annotation tells callers they can use the response object for body reads, which they cannot safely do.This bug pre-exists the PR — the PR did not introduce it — but the PR is the first to assert a type contract that makes this silent footgun more visible and misleading. Annotating an incorrect type is worse than no annotation.
db_getreturn type is incomplete.The annotation
-> Optional[dict]is correct for thenone_okay=Truepath, but whennone_okay=Falsethe function either returnsdictor raises. The type is accurate in practice, but it loses precision: callers who always passnone_okay=Falsewill believe they may receiveNoneand add unnecessary guards. A more precise type would beOptional[dict]with a note, or a typed overload. This is minor and not a blocker on its own.Security
AUTH_TOKENcontains a hardcoded bearer token at line 7. This is a pre-existing issue and out of scope for this PR, but worth flagging for a future cleanup issue.Style & Conventions
Optionalimport addition is correct. No issues.db_calls.pyfile on disk (currentmain) still uses double-outer quotes in thelog_return_valueandplayer_descf-strings (lines 42-47, 186), meaning the diff shows the branch diverging from main on these cosmetic lines. These reformats are harmless but add noise to the diff.Suggestions
url_getto either: (a) read the body inside the context manager and returnbytes, or (b) annotate-> Noneand raise on non-200 (since the only "success" use case must read inside theasync with). The current annotation is a type lie that will mislead callers.timeoutno-op is correctly called out in the PR notes. Consider opening a follow-up issue if one doesn't already exist — it affects all six functions.int = None/dict = Noneparameter defaults should be addressed in a future typing cleanup pass.Verdict: REQUEST_CHANGES
The
url_get -> aiohttp.ClientResponseannotation is technically incorrect and actively misleading. The response object is returned from inside a closed context manager, meaning callers cannot safely use it for body reads. Shipping this annotation establishes a false type contract. The annotation should be corrected before merge — either fix the implementation to return body content, or annotate accurately.All other changes (five of the six annotations,
Optionalimport, f-string reformats) are correct and clean.Automated review by Claude PR Reviewer
AI Code Review
Files Reviewed
db_calls.py(modified)Findings
Correctness
db_get → Optional[dict]: Correct. The function returnsNoneon non-200 whennone_okay=True(line 80), and a parsed JSON dict on success.Optional[dict]accurately captures both paths.url_get → aiohttp.ClientResponse: Technically correct —ris aClientResponse. Pre-existing note:ris returned from insideasync with session.get(url) as r:, meaning the response is closed before the caller can read the body. This is a pre-existing design issue, out of scope here.db_patch / db_post / db_put / db_delete → dict: Correct for this API's contract — these either return parsed JSON (a dict for all known endpoints) or raiseValueError. Slightly imprecise in theory (r.json()can return list/str), but accurate in practice.Optionalcorrectly added to thefrom typing importline.Security
Tp3aO3jhYve5NJF1IqOmJTmk— this branch predates PR #44's credential cleanup. On merge, Git's 3-way merge should correctly preserve main's env-var approach since the PR branch didn't modify that line relative to its base. Worth a conscious check that the merge result usesPD_API_TOKENfrom env, not the hardcoded value.Style & Conventions
timeoutparameter is still accepted but not forwarded to aiohttp — correctly noted as pre-existing and out of scope.Suggestions
object_id: int = None,payload: dict = Noneetc. should beOptional[int],Optional[dict]— out of scope here but good candidates for a follow-up cleanup.Verdict: COMMENT
LGTM. Return type annotations are accurate and complete for all six async functions. Verify merge cleanly inherits the
PD_API_TOKENenv-var approach from main rather than the stale hardcoded token in this branch's context.Automated review by Claude PR Reviewer
AI Code Review
Files Reviewed
db_calls.py(modified)Findings
Correctness
url_getreturn type is still wrong (line 78).The fix commit (
82a8dac) changed the annotation fromaiohttp.ClientResponsetodict, but the function body returnsrdirectly — which is anaiohttp.ClientResponseobject, not a dict. The annotation is still incorrect. The pre-fix annotation (aiohttp.ClientResponse) was actually more accurate than the currentdict. The correct annotation depends on intent: if the function is supposed to deserialize the body, it shouldawait r.json()and return the result (adict); if it is supposed to return the raw response object, the annotation should beaiohttp.ClientResponse. As written, the type annotation actively misleads callers and will cause mypy/pyright to miss real type errors downstream.db_getcan returnaiohttp.ClientSessioncontext data, not justOptional[dict]. The-> Optional[dict]annotation fordb_getis correct and accurately reflects its behavior. No issue here.timeoutparameter is accepted but never forwarded (all six async functions). The PR body acknowledges this as a known pre-existing issue and explicitly scopes it out. Noted for completeness, not a blocker from this PR.Security
CRITICAL: Hardcoded bearer token on line 7.
This is a hardcoded API credential committed directly into source control. This PR introduced or retained this literal — it must be replaced with an environment variable lookup before merge. The correct pattern for this codebase is:
If this token was previously loaded from an environment variable and was regressed to a literal during this PR's changes, that regression must be reverted. Committing credentials to a repository (even a private one) is a serious security issue: the token is now in git history, visible to anyone with repo access, and cannot be safely rotated without also scrubbing history.
Action required: Rotate the
Tp3aO3jhYve5NJF1IqOmJTmktoken immediately, regardless of whether this PR is merged or closed. It is now in git history.Style & Conventions
The f-string quote normalization (double → single for nested expressions) is cosmetic and consistent. No issues.
Optionalimport addition is correct and follows the existingfrom typing importpattern.All six function signatures follow a consistent annotation style. No issues with formatting or structure.
Suggestions
url_get, decide whether it should return a raw response or a deserialized dict, then make the implementation and annotation agree. Given that all other DB functions returndictafter callingawait r.json(), the most consistent fix is to deserialize inurl_getas well and annotate-> dict.int = None/dict = Noneparameter annotations (should beOptional[int], etc.) are noted in the PR body as out of scope. A follow-up issue to fix these would prevent future confusion, since this PR's goal is enabling accurate static analysis.Verdict: REQUEST_CHANGES
Two issues must be resolved before merge:
url_getreturn type annotation (-> dict) does not match the actual return value (aiohttp.ClientResponse). The annotation must match the implementation, otherwise this PR's stated goal — enabling accurate static analysis — is undermined at this function specifically.Automated review by Claude PR Reviewer
@ -5,3 +4,4 @@from typing import Literal, Optionalfrom exceptions import loggerAUTH_TOKEN = {"Authorization": "Bearer Tp3aO3jhYve5NJF1IqOmJTmk"}SECURITY: Hardcoded bearer token. This credential is now in git history and must be rotated immediately. Replace with an environment variable lookup:
@ -76,7 +76,7 @@ async def db_get(raise ValueError(f"DB: {e}")TYPE ANNOTATION MISMATCH: The annotation
-> dictdoes not match what is actually returned. Whenr.status == 200, the function returnsr, which is anaiohttp.ClientResponse, not adict. Either:return await r.json()and keep-> dict-> aiohttp.ClientResponseThe pre-fix annotation (
aiohttp.ClientResponse) was more accurate than the currentdict.Looks good — return type annotations are accurate and the Optional import addition is correct. Approving for merge.