fix(rest): PaginatedIterator continues past an empty page with a next link (#3.3)#58
Merged
Conversation
… link (#3.3) The termination check in PaginatedIterator._fetch_next was `if next_url and data:` — it stopped iterating as soon as a page returned an empty `data` array, even when that same page carried a `links.next` cursor. A page can legitimately have zero items on THIS page while more pages exist (e.g. a filtered page that matches nothing at this cursor). The `and data` clause marked the iterator done and silently dropped every subsequent page. Fix: drive termination ONLY by the absence of a next link. Change the condition to `if next_url:` so an empty-but-not-last page still fetches the next page via its cursor. Regression test: test_empty_page_with_next_continues stages page 1 = {data: [], links.next: <cursor>} and page 2 = {data: [item], links: {}} over the shared mock, then asserts the iterator yields page 2's item and issued 2 GETs (the second carrying the parsed cursor). This test FAILS against the old `next_url and data` code (collects `[]`, page 2 never fetched) and PASSES after the fix. No public surface change (bugfix, no SEMVER bump). Full run-ci.sh green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqshDQajCmDHD3xPo4CXMC
porting-sdk #99 widens accessor_truth.py + doc_env.py to read ALL tracked markdown (not just docs/**), surfacing real pre-existing doc drift. DOC-ENV — correct stale env-var names to the vars the SDK actually reads: - SWML_AUTH_USER -> SWML_BASIC_AUTH_USER - SWML_AUTH_PASS -> SWML_BASIC_AUTH_PASSWORD (auth_mixin.py / security_config.py read SWML_BASIC_AUTH_USER/PASSWORD) - SWML_LOG_LEVEL -> SIGNALWIRE_LOG_LEVEL (logging_config.py reads SIGNALWIRE_LOG_LEVEL) - SWML_BASIC_AUTH_PASSWORD_FILE: removed — the SDK never reads a *_FILE var; reworked the docker tutorial to use an env_file with the real SWML_BASIC_AUTH_USER/PASSWORD the SDK reads directly. ACCESSOR-TRUTH — math skill README described a phantom `eval()`: - the skill does NOT use Python's eval(); it parses to an AST and walks it with a restricted evaluator (ast.parse(mode="eval") + _safe_eval). Rewrote the "Safe Evaluation" section to describe the real AST-based implementation. Not touched (flagged for human decision, deliberately NOT allowlisted): SWML_SCHEMA_PATH / SWML_SCHEMA_MCP_DEBUG are documented in mcp/swml-schema-search/README.md and are genuinely read by the sibling mcp/swml-schema-search/swml_schema_mcp.py. The doc-gate flags them only because its code perimeter is signalwire/signalwire/**/*.py (shippable SDK surface) and excludes the standalone mcp/ tool. Docs and code are both correct; the mismatch is a perimeter artifact, not doc drift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqshDQajCmDHD3xPo4CXMC
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.
The bug
PaginatedIterator._fetch_next(signalwire/signalwire/rest/_pagination.py) terminated pagination with:The
and dataclause is wrong. A page can legitimately return an emptydataarray on THIS page while still carrying alinks.nextcursor (more pages exist) — e.g. a filtered page that matches nothing at the current cursor. On such a page the old condition fell into theelse, set_done = True, and silently dropped every subsequent page. Any real items living beyond an empty intermediate page were never yielded.The fix
Termination is now driven only by the absence of a next link, not by an empty data array:
An empty-but-not-last page still parses its
links.nextcursor and fetches the next page. No public surface change — this is a pure bugfix (SEMVER-DIFF gate confirms no version bump required).Regression test (proven to fail on old code)
Added
TestPaginatedIterator::test_empty_page_with_next_continuesintests/unit/rest/test_pagination_mock.py, driven over the sharedmock_signalwireserver:{data: [], links.next: ".../addresses?cursor=page2"}(empty, but more pages){data: [{id: "addr-late"}], links: {}}(terminal)It asserts the iterator yields
addr-lateand issued 2 GETs (the second carryingcursor=page2).if next_url and data:code: FAILS —collected == [], page 2 is never fetched (iterator marked done on the empty page 1).Verification
PORTING_SDK=/Users/michaeljerris/src/porting-sdk bash scripts/run-ci.sh→ CI PASS (all gates green, including TEST, TYPECHECK, LINT, FMT, REST-COVERAGE, SPEC-PARITY, PAGINATION-WIRED, SEMVER-DIFF).🤖 Generated with Claude Code
https://claude.ai/code/session_01PqshDQajCmDHD3xPo4CXMC