Skip to content

auto_paging_iter does not paginate backwards if starting_after is None #1562

@ryancausey

Description

@ryancausey

Describe the bug

I was working with the auto_paging_iter to implement Relay pagination over invoices, and I encountered an interesting quirk in the way it is implemented. The auto_paging_iter code currently checks if ending_before is in the retrieve params and starting_after is not to decide to reverse the results and paginate backwards:

def _auto_paging_iter(self) -> Iterator[T]:
    page = self

    while True:
        if (
            "ending_before" in self._retrieve_params
            and "starting_after" not in self._retrieve_params
        ):
            for item in reversed(page):
                yield item
            page = page.previous_page()
        else:
            for item in page:
                yield item
            page = page.next_page()

        if page.is_empty:
            break

In my code I am passing along all input args because, at least in the services model, the SDK correctly ignores and drops any params passed as None:

for stripe_invoice in stripe_client.v1.invoices.list(
    params={
        "limit": 100,
        "customer": dynamodb_alarm_user.customer_id,
        "starting_after": parsed_input.after,
        "ending_before": parsed_input.before,
        "expand": ("data.customer",),
    },
    options=merge_default_options(
        {"stripe_account": dynamodb_jurisdiction.account_id}
    ),
).auto_paging_iter():

This leads to the following params being shown in the request logs on Stripe's side:

{
  "customer": "cus_SyBoRWyoh9GtxR",
  "ending_before": "in_1S2FQhBARyXc9bEw3n0eQQwj",
  "expand": {
    "0": "data.customer"
  },
  "limit": "100"
}

However, because starting_after is present in self._retrieve_params even though it is None, the auto_paging_iter logic falls into the else cases and does not reverse the results and calls page.next_page() instead of page.previous_page().

This leads to an incorrect pagination after the first page of results, because it uses the wrong cursors and arguments to retrieve the next page.

To Reproduce

  1. create N invoices to be able to paginate over under the same stripe account.
  2. use stripe_client.v1.invoices.list(params={"limit": 1, "starting_after": None, "ending_before": invoices[-1].id}).auto_paging_iter() to try and iterate the results.

Expected behavior

The auto paging iter should iterate backwards over the invoices list from the end to the beginning, excluding the invoice specified as ending_before.

Code snippets

OS

Ubuntu 22.04.5 LTS

Language version

Python 3.13.3

Library version

12.5.0

API version

2025-08-27.basil

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions