Convert Overdrive API responses to pydantic models (PP-3175)#3169
Convert Overdrive API responses to pydantic models (PP-3175)#3169dbernstein wants to merge 1 commit intomainfrom
Conversation
c446363 to
4922d7a
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3169 +/- ##
=======================================
Coverage 93.30% 93.31%
=======================================
Files 502 502
Lines 46155 46172 +17
Branches 6318 6315 -3
=======================================
+ Hits 43067 43086 +19
Misses 2003 2003
+ Partials 1085 1083 -2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
4922d7a to
f2da1fd
Compare
f2da1fd to
d4dee98
Compare
|
Claude finished @dbernstein's task in 3m 22s —— View job Review complete
Clean, well-scoped refactor that eliminates raw dict access in favour of typed Pydantic models throughout the availability flow. The new models follow existing |
| if patrons_in_hold_queue is None: | ||
| patrons_in_hold_queue = 0 | ||
| patrons_in_hold_queue += book["numberOfHolds"] | ||
| patrons_in_hold_queue = availability.number_of_holds |
There was a problem hiding this comment.
Minor: This is a subtle behavioral change from the old code. Previously, patrons_in_hold_queue was only set when "numberOfHolds" was present in the dict (leaving it as None otherwise); CirculationData with patrons_in_hold_queue=None means "don't overwrite the stored value," while 0 means "set to zero." Since number_of_holds defaults to 0, any availability response that omits numberOfHolds will now explicitly zero out the hold queue instead of leaving it unchanged. The Overdrive API almost certainly always includes this field for owned titles, so practical impact is likely nil — but worth confirming the default is intentional.
There was a problem hiding this comment.
I didn't see anything in the OverDrive docs to suggest that numberOfHolds would be missing or suggests anything other than 0 holds.
| # Use the caller-provided ID for LicensePool lookup. This is always | ||
| # set because circulation_lookup creates dict(id=book_id) when called | ||
| # with a string, and book-list dicts always include "id". | ||
| resolved_book_id = cast(str, book.get("id")) |
There was a problem hiding this comment.
Nit: cast(str, book.get("id")) suppresses the None case at the type level without a runtime check. The comment explains why "id" is always present, but an assert (or assert book.get("id") is not None) would surface a violation immediately rather than letting None propagate to LicensePool.for_foreign_id with a harder-to-diagnose error.
There was a problem hiding this comment.
Okay: we should fix that.
Description
This PR converts Overdrive API dict-based response objects to typed, validated Pydantic models (following the existing
BaseOverdriveModel/ OPDS2 conventions).This PR covers the availability document — the response from the Overdrive availability endpoint used by
update_licensepooland the async importer.New models (
model.py)AvailabilityType—StrEnumforNormal,AlwaysAvailable,LimitedAvailabilityAvailabilityAccount— per-library copy counts (id,copiesOwned,copiesAvailable,shared)Availability— full availability document (reserveId,accounts,availabilityType,available,copiesOwned/Available,numberOfHolds,isOwnedByCollections,errorCode,links)reserveIdis optional to accommodate error responses (e.g.errorCode: "NotFound") that omit it.Refactors
representation.py:book_info_to_circulation(book: dict)→book_info_to_circulation(availability: Availability, book_id: str | None = None).book_idtakes precedence overreserve_idso callers with a known ID (e.g. after a circulation lookup) remain authoritative._get_applicable_accountsnow works withlist[AvailabilityAccount].api.py:update_licensepoolcallsAvailability.model_validate_json(content)instead ofbook.update(json.loads(content)).update_licensepool_with_book_infosignature updated to acceptAvailability+ explicitbook_id.importer.py:availabilityV2dict is validated throughAvailability.model_validate(...)before being passed tobook_info_to_circulation.Tests
TestAvailabilityadded totest_model.pycovering all fixture files (overdrive_availability_information*.json,overdrive_availability_advantage.json,overdrive_availability_not_found.json) and enum/default validation.test_representation.pyupdated to constructAvailabilityobjects rather than raw dicts.test_api.pyupdated to use newupdate_licensepool_with_book_infosignature.Checklist
mypypasses with no new errorsBaseOverdriveModelconventions (frozen, aliases, specific types)