Skip to content

Commit 43c3d69

Browse files
committed
REST: Add pagination support for list_views
1 parent 6e68c9c commit 43c3d69

2 files changed

Lines changed: 74 additions & 6 deletions

File tree

pyiceberg/catalog/rest/__init__.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ class ListTablesResponse(IcebergBaseModel):
370370

371371
class ListViewsResponse(IcebergBaseModel):
372372
identifiers: list[ListViewResponseEntry] = Field()
373+
next_page_token: str | None = Field(default=None, alias="next-page-token")
373374

374375

375376
_PLANNING_RESPONSE_ADAPTER = TypeAdapter(PlanningResponse)
@@ -1102,12 +1103,31 @@ def list_views(self, namespace: str | Identifier) -> list[Identifier]:
11021103
return []
11031104
namespace_tuple = self._check_valid_namespace_identifier(namespace)
11041105
namespace_concat = self._encode_namespace_path(namespace_tuple)
1105-
response = self._session.get(self.url(Endpoints.list_views, namespace=namespace_concat))
1106-
try:
1107-
response.raise_for_status()
1108-
except HTTPError as exc:
1109-
_handle_non_200_response(exc, {404: NoSuchNamespaceError})
1110-
return [(*view.namespace, view.name) for view in ListViewsResponse.model_validate_json(response.text).identifiers]
1106+
1107+
all_identifiers: list[Identifier] = []
1108+
page_token: str | None = None
1109+
1110+
while True:
1111+
# Build URL with pagination params
1112+
url = self.url(Endpoints.list_views, namespace=namespace_concat)
1113+
if page_token:
1114+
url = f"{url}?pageToken={page_token}"
1115+
1116+
response = self._session.get(url)
1117+
try:
1118+
response.raise_for_status()
1119+
except HTTPError as exc:
1120+
_handle_non_200_response(exc, {404: NoSuchNamespaceError})
1121+
1122+
parsed = ListViewsResponse.model_validate_json(response.text)
1123+
all_identifiers.extend([(*view.namespace, view.name) for view in parsed.identifiers])
1124+
1125+
# Check if more pages exist
1126+
if not parsed.next_page_token:
1127+
break
1128+
page_token = parsed.next_page_token
1129+
1130+
return all_identifiers
11111131

11121132
@retry(**_RETRY_ARGS)
11131133
def commit_table(

tests/catalog/test_rest.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,54 @@ def test_list_views_200(rest_mock: Mocker) -> None:
639639
assert RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).list_views(namespace) == [("examples", "fooshare")]
640640

641641

642+
def test_list_views_paginated_200(rest_mock: Mocker) -> None:
643+
namespace = "examples"
644+
# First page with next-page-token
645+
rest_mock.get(
646+
f"{TEST_URI}v1/namespaces/{namespace}/views",
647+
json={
648+
"identifiers": [
649+
{"namespace": ["examples"], "name": "view1"},
650+
{"namespace": ["examples"], "name": "view2"},
651+
],
652+
"next-page-token": "page2token",
653+
},
654+
status_code=200,
655+
request_headers=TEST_HEADERS,
656+
)
657+
# Second page with next-page-token
658+
rest_mock.get(
659+
f"{TEST_URI}v1/namespaces/{namespace}/views?pageToken=page2token",
660+
json={
661+
"identifiers": [
662+
{"namespace": ["examples"], "name": "view3"},
663+
],
664+
"next-page-token": "page3token",
665+
},
666+
status_code=200,
667+
request_headers=TEST_HEADERS,
668+
)
669+
# Third page without next-page-token (last page)
670+
rest_mock.get(
671+
f"{TEST_URI}v1/namespaces/{namespace}/views?pageToken=page3token",
672+
json={
673+
"identifiers": [
674+
{"namespace": ["examples"], "name": "view4"},
675+
],
676+
},
677+
status_code=200,
678+
request_headers=TEST_HEADERS,
679+
)
680+
681+
result = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).list_views(namespace)
682+
assert result == [
683+
("examples", "view1"),
684+
("examples", "view2"),
685+
("examples", "view3"),
686+
("examples", "view4"),
687+
]
688+
689+
642690
def test_list_views_200_sigv4(rest_mock: Mocker) -> None:
643691
namespace = "examples"
644692
rest_mock.get(

0 commit comments

Comments
 (0)