Skip to content

Commit bfcfa9a

Browse files
committed
fix: follow offset cursors in either direction
1 parent 80908e4 commit bfcfa9a

2 files changed

Lines changed: 16 additions & 9 deletions

File tree

‎src/kernel/pagination.py‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,10 @@ def next_page_info(self) -> Optional[PageInfo]:
133133
if next_offset == 0:
134134
return None
135135

136-
# A next offset at or before the one this page was requested at means no
137-
# progress (e.g. an empty page after rows vanished mid-request); stop
138-
# instead of re-requesting the same page.
136+
# Cursors can move in either direction. Stop only when the server repeats
137+
# the cursor and would re-request the same page.
139138
request_offset = self._options.params.get("offset")
140-
if isinstance(request_offset, int) and next_offset <= request_offset:
139+
if isinstance(request_offset, int) and next_offset == request_offset:
141140
return None
142141

143142
return PageInfo(params={"offset": next_offset})
@@ -191,11 +190,10 @@ def next_page_info(self) -> Optional[PageInfo]:
191190
if next_offset == 0:
192191
return None
193192

194-
# A next offset at or before the one this page was requested at means no
195-
# progress (e.g. an empty page after rows vanished mid-request); stop
196-
# instead of re-requesting the same page.
193+
# Cursors can move in either direction. Stop only when the server repeats
194+
# the cursor and would re-request the same page.
197195
request_offset = self._options.params.get("offset")
198-
if isinstance(request_offset, int) and next_offset <= request_offset:
196+
if isinstance(request_offset, int) and next_offset == request_offset:
199197
return None
200198

201199
return PageInfo(params={"offset": next_offset})

‎tests/test_pagination.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,21 @@ def test_stops_on_empty_page_when_x_has_more_false(cls: PageClass) -> None:
8989

9090

9191
@both_classes
92-
def test_stops_when_next_offset_does_not_advance(cls: PageClass) -> None:
92+
def test_stops_when_next_offset_repeats_requested_offset(cls: PageClass) -> None:
9393
page = _page(cls, items=[], next_offset=200, has_more=True, request_offset=200)
9494
assert page.next_page_info() is None
9595
assert page.has_next_page() is False
9696

9797

98+
@both_classes
99+
def test_continues_past_empty_page_with_descending_cursor(cls: PageClass) -> None:
100+
page = _page(cls, items=[], next_offset=200, has_more=True, request_offset=300)
101+
assert page.has_next_page() is True
102+
info = page.next_page_info()
103+
assert info is not None
104+
assert info.params == {"offset": 200}
105+
106+
98107
@both_classes
99108
def test_stops_on_empty_first_page_without_pagination_headers(cls: PageClass) -> None:
100109
page = _page(cls, items=[], next_offset=None, has_more=None)

0 commit comments

Comments
 (0)