Repository navigation
fix(client): follow pagination and verify counts in list getters - #29
Merged
Merged
Conversation
get_bundles(parent=...), get_bitstreams(bundle=...) and get_resourcepolicy() fetched a single page and returned its _embedded list, discarding the HAL page object. get_resourcepolicy did not even pass a size, so it relied on the server default. Whenever a resource had more elements than one page holds, or the server capped the page size below the requested one, the caller got a silently truncated list. Downstream audits read a truncated policy list as "the file has no such policy" (eg. a missing Anonymous READ), and nothing could prove completeness after the fact. A shared private helper, _fetch_all_pages, now backs the three getters: - it requests the first page with the caller's params, then every following page up to the first response's totalPages, reusing the same params (incl. size); the loop is bounded by that first response, so a server repeating a page cannot spin forever; - when the response carries a page object, the collected count must equal totalElements (minus the elements before a non-zero start page), otherwise it raises RuntimeError naming the url, collected and expected counts; - a failure on any later page raises RuntimeError with the HTTP status and url instead of returning the partial list. A response without a page object keeps the old single-page behaviour, and the first request is unchanged (get_resourcepolicy still sends no page param). All existing contracts stay: 404 -> [] with the INFO log for bundles and bitstreams, RuntimeError on other first-page failures, get_resourcepolicy's None on a failed first request or an invalid UUID, and its [] when _embedded is missing - now only when totalElements is 0 or absent. The single-uuid get_bundles(uuid=...) path is untouched.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Repeated server pages can satisfy the count check while returning incomplete, duplicated results.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Adds complete HAL pagination and count verification to prevent silently truncated results.
Changes:
- Introduces a shared paginated-fetch helper.
- Applies it to bundles, bitstreams, and resource policies.
- Adds pagination, mismatch, and failure tests.
| File | Description |
|---|---|
dspace_rest_client/client.py |
Implements and integrates pagination. |
tests/_helpers.py |
Adds a paginated HAL response builder. |
tests/test_client_read.py |
Tests pagination and failure behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ed one A server that ignores the page parameter and repeats an earlier page can still make the collected count match totalElements, returning duplicates and silently missing the real page. Every later page's page.number must now equal the page that was asked for; otherwise the getter raises.
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
get_bundles(parent=...),get_bitstreams(bundle=...)andget_resourcepolicy()fetched a single page and returned its_embeddedlist, discarding the HALpageobject.get_resourcepolicydid not even pass asize, so it relied on the server default.Whenever a resource had more elements than one page holds, or the server capped the page size below the requested one, the caller silently got a truncated list. Downstream audits read a truncated policy list as "the file has no such policy" (e.g. a missing Anonymous READ), and nothing could prove completeness after the fact.
Solution
One private helper,
_fetch_all_pages, backs the three getters:totalPages, reusing the same params (incl.size). The loop is bounded by the first response, so a server repeating a page cannot spin forever.pageobject, the collected count must equaltotalElements. On a mismatch it raisesRuntimeErrornaming the URL and the collected and expected counts.RuntimeErrorwith the HTTP status and URL. It never returns the partial list.Unchanged
pageobject behaves as before (single page).get_resourcepolicystill sends nopageparam.[]with the INFO log, and other first-page failures still raiseRuntimeError.get_resourcepolicystill returnsNoneon a failed first request or an invalid UUID. It still returns[]when_embeddedis missing, but now only whentotalElementsis 0 or absent.get_bundles(uuid=...)path is untouched.Tests
New cases in
tests/test_client_read.py(with apaged()body builder intests/_helpers.py):page/sizeparams.totalElements(bundles 2 of 3, bitstreams 4 of 5) raiseRuntimeError.RuntimeErrorfor all three getters._embeddedwith a non-zero total raises, and with a zero total it returns[].All existing tests pass unchanged.