Guard against pagination loops and silent truncation in execute() - #5
Conversation
Auto-pagination in execute() is driven by the shape of the caller's query, which can produce two failure modes: - A connection query that selects `pageInfo` but does not declare/pass `$after` never advances: the same page (same `endCursor`) is returned with `hasNextPage: true` indefinitely. Detect a non-advancing cursor and stop, with a warning to wire `after: $after`. - A response that returns only a first page (a `nodes` list with a larger `count` and no `pageInfo`, or a `data` list with `hasMore` / `nextCursor` / a larger `total`) is silently incomplete. Warn so a first page isn't mistaken for the whole result. Add tests/test_pagination.py covering both guards, the max_records cap, and no-false-positive cases (complete result, final page with a populated cursor).
ReviewThis PR adds two safety guards to 1. A response with one paginated connection field and one separately-truncated field silently drops the second field's excess data with zero warning
The truncation-check loop only runs when 2. The non-advancing-cursor guard converts a loud, easy-to-catch bug into a silent one, specifically for this client's main consumerSame file, ~lines 97-113 Before this PR, an under-wired query (missing Relatedly: the returned 3.
|
…n-bool hasMore
- execute(): after auto-pagination, set the returned connection's pageInfo to
the last fetched page so an incomplete result (record cap hit or the
non-advancing-cursor guard firing) is detectable from the return value, not
only from stderr.
- Run the truncation scan over ALL top-level fields (new _warn_if_truncated),
so a truncated data/hasMore field or a second connection returned alongside
the auto-paginated one is still reported.
- Treat max_records=0 as a real cap (0 records) via `is not None`, not a falsy
"no cap".
- Accept non-bool hasMore ("true"/"false" strings) when judging truncation.
- Add tests for each of the above.
|
Thanks for the review — all points addressed in 3cec386:
Added tests for each (18 pass). |
* Release 1.5.20260720: publish pagination loop + truncation guards Bump the version so the pagination-correctness guards merged in #5 (cursor non-advancing guard, silent-truncation warnings, accurate returned pageInfo, max_records=0 handling) publish to PyPI. The #5 merge changed main but left the version at 1.5.20260601 (already on PyPI), so auto-publish was skipped. * Sync uv.lock with the 1.5.20260720 version bump * Correct version to 1.6.20260601 and clarify the versioning scheme The prior bump changed the YYYYMMDD (schema-date) portion, which implies a new schema. No schema changed — the pagination guards are an additive library change, so bump the minor: 1.5.20260601 -> 1.6.20260601, schema date unchanged. Sharpen CLAUDE.md's Versioning section so the schema-date portion is never bumped for a code change.
What
Two safety guards in
RSCClient.execute()auto-pagination:pageInfobut doesn't declare/pass$after(the server keeps returning page 1 withhasNextPage: true).nodes+countwithoutpageInfo, and adatalist withhasMore/total/nextCursor.Correctly-wired pagination and the existing
max_recordscap are unchanged.Why
execute()auto-paginates based on the query's shape, so an under-wired query could hang (loop) or silently return a partial result. These guards make both failure modes safe without changing correct behavior.Tests
tests/test_pagination.py(runs under the existingpytest tests/CI job): loop guard (incl.endCursor=None), correct pagination, both truncation shapes, per-field,max_recordscap, and no-false-positive cases (complete result; final page with a populated cursor).