fix(python): derive offset pagination _has_next instead of hardcoding True#16155
Conversation
… True The Python SDK generator's offset paginator emitted `_has_next = True` unconditionally, so `while pager.has_next: pager = pager.next_page()` loops never terminated. It also ignored the IR's optional `OffsetPagination.has_next_page` response property — even users who explicitly declared `has-next-page` got the same hardcoded `True`. `init_has_next()` now: - emits `bool(_parsed_response.<path>)` when `has-next-page` is set on the pagination config, and - falls back to `len(_items or []) > 0` otherwise, matching the TypeScript generator's `(r?.data ?? []).length > 0` semantics.
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
…l responses Two latent crashes in the offset paginator, surfaced by code review: 1. Nested `has-next-page` paths dereferenced intermediate segments with no None-guard. `has-next-page: $response.metadata.hasNext` with a null `metadata` raised AttributeError. `get_next_none_safe_condition()` now returns the same null-guard expression `CursorPagination` uses, so the block runs under `if _parsed_response.metadata is not None:`. 2. `init_custom_vars_pre_next` was a no-op, so endpoints with an optional response type left `_has_next`/`_get_next` unbound when the server returned a null body — `SyncPager(has_next=_has_next, ...)` then raised NameError. Now pre-initializes both to `False`/`None` (matching cursor), so the conditional guard always has fallback values. Neither path is exercised by current seed fixtures (no nested `has-next-page` in test definitions, and no offset endpoint with `response: optional<T>`), so snapshots are unchanged.
Closes FER-10931.
Summary
The Python SDK generator's offset paginator hardcoded
_has_next = Truefor every generatedlistendpoint, which caused a couple problems:while pager.has_next: pager = pager.next_page()never terminated;for x in pagerworked only because the core paginator stops whenget_next()returns an empty page.It also ignored the IR's optional
OffsetPagination.has_next_pageresponse property, so users who explicitly declaredhas-next-page: $response.<field>still got the hardcodedTrue.Reported by a customer using offset pagination via
x-fern-pagination: { offset: $request.page, results: $response.data }.Change
init_has_next()ingenerators/python/.../pagination/offset.pynow:Items-length fallback matches the TypeScript generator's
hasNextPage: (r) => (r?.data ?? []).length > 0.Before / after (generated)
And for endpoints that explicitly declare
has-next-page: $response.hasNextPage:Test plan
pnpm seed test --generator python-sdk --fixture pagination --skip-scripts --localregenerated all 3 offset-pagination fixtures; snapshots updated in this PR._has_next = Truestrings inseed/python-sdk/.