Skip to content

Give OverdrivePatronRequests typed endpoint wrappers (PP-4938) - #3685

Open
jonathangreen wants to merge 4 commits into
chore/overdrive-request-specfrom
chore/overdrive-patron-endpoints
Open

Give OverdrivePatronRequests typed endpoint wrappers (PP-4938)#3685
jonathangreen wants to merge 4 commits into
chore/overdrive-request-specfrom
chore/overdrive-patron-endpoints

Conversation

@jonathangreen

@jonathangreen jonathangreen commented Aug 31, 2026

Copy link
Copy Markdown
Member

Description

Adds a named method per patron endpoint, each taking primitives and returning a validated model: get_checkouts, get_checkout, create_checkout, get_holds, create_hold, delete_hold and get_patron_information.

All the patron business methods in OverdriveAPI are migrated onto them, and the patron_request delegator on the API class is removed.

Motivation and Context

Part of separating the Overdrive integration's HTTP concerns from its business logic (PP-4938). This is the step that makes the API class an adapter rather than a request builder: it no longer assembles URLs or request specs for fixed endpoints.

The hold notification email choice moves into create_hold. Which field Overdrive expects, emailAddress or ignoreHoldEmail, is a property of the endpoint rather than of our business logic, so it belongs with the request rather than at the call site.

Hypermedia actions still go through OverdrivePatronRequests.patron_request directly, because the request they describe comes from the response document rather than from a fixed endpoint. That is the distinction the wrappers draw: fixed endpoints get a named method, and links the API document hands us do not. follow_download_link sits alongside them but is named for what it does, since the link it follows comes from the loan document rather than from a template we own.

CHECKOUT_ENDPOINT returns in this PR. It was dropped earlier in the series as a template with no callers; get_checkout is the caller it was missing, and using it keeps that method the same shape as delete_hold.

flask.request usage stays in the API layer, in _contentlink_fulfillment.

How Has This Been Tested?

Adds coverage for each wrapper to the existing TestOverdrivePatronRequests: the verb, the URL including the upper-casing of the identifier in get_checkout, and the serialized field payloads for create_checkout and both branches of create_hold.

The business tests in test_api.py survive nearly unchanged, which is the evidence that the requests on the wire are identical: they queue raw HTTP responses in strict order and assert on the recorded URLs, verbs and payloads.

tox -e py312-docker -- tests/manager/

6002 passed. mypy clean.

Checklist

  • I have updated the documentation accordingly.
  • All new and existing tests passed.

@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @jonathangreen's task in 3m 43s —— View job


Summary

Clean, faithful refactor. I traced each migrated call site against its pre-change form and the requests on the wire are identical: get_checkout reproduces the old f"{CHECKOUTS_ENDPOINT}/{id.upper()}" exactly (endpoint() interpolates patron_host and overdrive_id in one pass, and the later endpoint() calls inside patron_request/_do_request short-circuit on the already-resolved URL), create_hold preserves the old field order and truthiness branch, and delete_hold keeps the same non-uppercased identifier. Imports removed from api.py (overload, Response, RequestSpec, build_field_request, PatronInformation, BaseOverdriveModel, Hold as HoldResponse) are all genuinely unused now, and patron_request has no remaining callers outside requests.py and the three hypermedia sites the PR deliberately keeps. The three GET wrappers assert isinstance on a real sample payload, and both POST wrappers pin the serialized body, so the "returns a validated model" claim is actually exercised rather than only type-checked. One naming nit below; nothing blocking.

Details

Nit: src/palace/manager/integration/license/overdrive/requests.py:506

79ef328 renamed delete_hold's parameter from product_id to overdrive_id so callers pass "the same overdrive_id every other wrapper takes", but the template placeholder it feeds is still %(product_id)s, while the CHECKOUT_ENDPOINT this PR reintroduces one line above uses %(overdrive_id)s for the same concept. Since HOLD_ENDPOINT has exactly one consumer (line 755), renaming the placeholder is self-contained and finishes the change already made to the parameter:

HOLD_ENDPOINT = "%(patron_host)s/v1/patrons/me/holds/%(overdrive_id)s"
...
url = self.endpoint(self.HOLD_ENDPOINT, overdrive_id=overdrive_id)

HOLD_ENDPOINT = "%(patron_host)s/v1/patrons/me/holds/%(product_id)s"

| Branch: chore/overdrive-patron-endpoints

@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves fixed OverDrive patron operations behind typed request wrappers while retaining direct request handling for response-provided hypermedia links.

  • Adds typed wrappers for checkout, hold, patron-information, and download-link requests.
  • Migrates the OverDrive API business methods to those wrappers and removes its request delegator.
  • Adds request-level coverage for HTTP methods, endpoint construction, payload serialization, and response models.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/palace/manager/integration/license/overdrive/api.py Migrates patron business operations to typed request wrappers while preserving direct handling for hypermedia actions.
src/palace/manager/integration/license/overdrive/requests.py Adds typed wrappers that encapsulate fixed patron endpoint construction, request payloads, and response validation.
tests/manager/integration/license/overdrive/test_api.py Updates the token-refresh test to call the request-layer implementation directly.
tests/manager/integration/license/overdrive/test_requests.py Adds focused coverage for every new wrapper’s verb, URL, payload, and validated response type.

Sequence Diagram

sequenceDiagram
    participant API as OverdriveAPI
    participant Requests as OverdrivePatronRequests
    participant Token as PatronTokenProvider
    participant OD as OverDrive
    API->>Requests: Typed patron operation(primitives)
    Requests->>Token: Obtain patron access token
    Requests->>OD: Constructed authenticated HTTP request
    OD-->>Requests: Provider response
    Requests-->>API: Validated OverDrive model
Loading

Reviews (6): Last reviewed commit: "Pin the empty hold email and the checkou..." | Re-trigger Greptile

@jonathangreen
jonathangreen force-pushed the chore/overdrive-patron-endpoints branch from 59ac445 to f8d7a31 Compare August 31, 2026 17:12
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.57%. Comparing base (0d91db6) to head (2d04cd4).

Files with missing lines Patch % Lines
...alace/manager/integration/license/overdrive/api.py 91.66% 1 Missing ⚠️
Additional details and impacted files
@@                      Coverage Diff                      @@
##           chore/overdrive-request-spec    #3685   +/-   ##
=============================================================
  Coverage                         93.57%   93.57%           
=============================================================
  Files                               514      514           
  Lines                             46979    46992   +13     
  Branches                           6407     6407           
=============================================================
+ Hits                              43962    43975   +13     
  Misses                             1950     1950           
  Partials                           1067     1067           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The fixed patron endpoints get named methods that own their URL and
response type, so the API layer asks for a checkout or a hold rather than
assembling one. Hypermedia actions stay on patron_request, since the URL
comes from the document rather than from us.

CHECKOUT_ENDPOINT comes back with them. It was dropped earlier in this
series as an unused template; get_checkout is the caller it was missing,
and using it keeps that method the same shape as delete_hold.

follow_download_link is named for what it does rather than joining the
endpoint group, because the link it follows comes from the loan document.
@jonathangreen
jonathangreen force-pushed the chore/overdrive-patron-endpoints branch from f8d7a31 to 5ad7d87 Compare September 1, 2026 20:11
Call the hold identifier what its siblings call it. product_id was the
name of the placeholder in the URL template, not of the thing callers
pass, which is the same overdrive_id every other wrapper takes.

Note the odd one out. Only get_checkout uppercases the identifier, which
was invisible while these calls were spread across api.py and is obvious
now they sit together. Nothing on record says why, so say that rather than
letting a future reader make them uniform on a guess.
The branch tests truthiness, and the address is whatever Overdrive last
reported for the patron, so an empty string suppresses the notice the same
way a missing one does. Say that, so nobody adds a separate case for it.

Narrow the field type to what the method builds. An int was never one of
the values.
The previous commit documented that an empty address suppresses the hold
notice, but only None and a real address were covered, so an is None
refactor could have started sending an empty emailAddress with nothing to
catch it.

Assert the whole checkout path too, as the sibling tests do. Only the last
segment was checked, which would not have caught a typo in the template
this PR reintroduces.
@jonathangreen
jonathangreen requested a review from a team September 1, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant