fix: N+1 query in nearby endpoint, boundary test, photo media type #13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "12-fix-n-1-query-in-nearby-endpoint-add-bou"
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
Three fixes bundled: eliminates the N+1 query in the nearby endpoint's slot info lookup, adds a boundary test for the strict
>inequality onexpires_at, and infers the correct media type for receipt photo responses instead of hardcoding JPEG.Changes
src/mcd_tracker_api/routes/nearby.py: Replaced per-location_build_slot_info()(1-2 queries per matched location inside the loop) with_batch_slot_info()that issues exactly 2 queries total usingWHERE location_id IN (...)withGROUP BY. Two-pass approach: first pass collects matched location IDs, batch query fetches all slot data, second pass assembles response objects.src/mcd_tracker_api/routes/receipts.py:FileResponsenow infersmedia_typefrom the file extension (.jpg,.jpeg,.png,.webp) instead of hardcodingimage/jpeg.tests/test_locations.py: Addedtest_code_at_exact_30_day_boundary_not_active-- creates 5 codes withexpires_at = nowexactly, verifies a 6th code succeeds becauseexpires_at > now()excludes the exact boundary.tests/test_receipts.py: Addedtest_get_receipt_photo_png_media_type-- verifies PNG files returnimage/pngcontent-type header.Test Plan
ruff formatandruff checkclean>inequality contractReview Checklist
Related
Self-Review: PASS
Reviewed the diff across all 4 changed files. No issues found.
N+1 Fix (nearby.py)
_batch_slot_info()correctly replaces per-location_build_slot_info()with two batched queries usingWHERE IN+GROUP BYlocation_idsprevents unnecessary DB callsactive_map.get(loc_id, 0)correctly handles locations with zero active codes (not in GROUP BY result)Boundary Test (test_locations.py)
expires_at=nowexactly, asserts 6th code returns 201 (not 409)>inequality in_count_active_codesPhoto Media Type (receipts.py)
ALLOWED_EXTENSIONS(.jpg, .jpeg, .png, .webp)image/jpegis safe for edge casesimage/pngcontent-type headerTest Results
PR #13 Review
DOMAIN REVIEW
Tech stack: Python 3.14 / FastAPI / SQLAlchemy ORM / Pydantic / Postgres 16
Batch query correctness -- The
_batch_slot_info()refactor innearby.pyis correct. The two batched queries useWHERE location_id IN (...)withGROUP BY, and the filter conditions (keycloak_sub ==,expires_at > func.now()) are identical to the original per-location_build_slot_info(). The two-pass approach (first pass collects matched IDs into a list, batch query fetches all slot data at once, second pass assembles response objects) eliminates the N+1 and caps the endpoint at exactly 3 DB queries regardless of how many nearby locations match saved ones (1 for saved locations, 1 for active counts, 1 for reopen times -- the reopen query only fires if any location is full). The empty-list early return on line 54 prevents a degenerateIN ()clause.The
matched_location_idsdeduplication check (line 141:if match.id not in matched_location_ids) is O(n) on a list rather than a set, but the list size is bounded by the user's saved locations count, which is small in practice. Not worth blocking over.Boundary test analysis --
test_code_at_exact_30_day_boundary_not_activesetsexpires_at = datetime.now(UTC).replace(tzinfo=None)in Python, then issues a POST that internally queriesCouponUsage.expires_at > func.now(). Sincefunc.now()is evaluated by Postgres at query execution time (a few milliseconds after the Pythonnow), the storedexpires_atis actually less thanfunc.now()by the time the query runs, not equal. The test passes correctly and validates that expired-or-boundary codes do not count as active, but the exact boundary claim (equality) is technically not exercised due to this clock gap. This is a known limitation of testing DB server-sidenow()vs application-sidedatetime.now()-- the test is still valuable as a regression guard.Photo media type -- The
media_type_mapinreceipts.pycovers all four entries inALLOWED_EXTENSIONS(.jpg,.jpeg,.png,.webp). The fallback toimage/jpegfor unknown extensions is reasonable since uploads are already gated byALLOWED_EXTENSIONS. The new test creates a real temp file with PNG magic bytes and assertscontent-type == "image/png".PEP compliance -- Type hints present throughout (
dict[int, tuple[int, int, datetime | None]]). Docstrings on both new functions. Variable names are clear.BLOCKERS
None.
All three changes have corresponding test coverage:
test_code_at_exact_30_day_boundary_not_activeadded.test_get_receipt_photo_png_media_typeadded.No unvalidated user input, no secrets, no DRY violations in auth paths.
NITS
matched_location_idscould be a set (nearby.pyline 136) -- Using alistwithnot inmembership checks is O(n) per lookup. Asetwould be O(1). Not a performance concern at current scale, but it is the more idiomatic structure for deduplication in Python.DRY observation across slot-counting logic -- There are now three independent implementations of the "count active codes" concept:
_count_active_codes()inlocations.py(single location, single query)_batch_slot_info()innearby.py(multiple locations, batched)case(CouponUsage.expires_at > func.now(), ...)indashboard.py(JOIN-based)All three use the same
expires_at > func.now()inequality, so they are semantically consistent. This is not a blocker (none are auth/security paths), but if the active-code definition ever changes (e.g., adding aredeemedexclusion), three places need updating. Consider extracting a shared filter builder in a future refactor.Boundary test timing caveat -- As noted in the domain review, the test's claim that
expires_at == nowis not strictly exercised because Postgresnow()evaluates a few milliseconds later. A more precise version could insertexpires_at = now + timedelta(seconds=1)(clearly in the future) alongside the boundary rows to prove the inequality is strict. Low priority -- the test still catches the right class of regressions.SOP COMPLIANCE
12-fix-n-1-query-in-nearby-endpoint-add-boureferences issue #12)Closes #12Note: PR body uses
## Review Checklistinstead of## Relatedas the section name for the closing reference -- theCloses #12line is at the bottom under## Related. Template compliance is met.PROCESS OBSERVATIONS
.jpgfiles).VERDICT: APPROVED