Skip to content

Commit e98633a

Browse files
committed
REST: Add pagination support for list_tables
1 parent 6e68c9c commit e98633a

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
@@ -366,6 +366,7 @@ class ListViewResponseEntry(IcebergBaseModel):
366366

367367
class ListTablesResponse(IcebergBaseModel):
368368
identifiers: list[ListTableResponseEntry] = Field()
369+
next_page_token: str | None = Field(default=None, alias="next-page-token")
369370

370371

371372
class ListViewsResponse(IcebergBaseModel):
@@ -1016,12 +1017,31 @@ def list_tables(self, namespace: str | Identifier) -> list[Identifier]:
10161017
self._check_endpoint(Capability.V1_LIST_TABLES)
10171018
namespace_tuple = self._check_valid_namespace_identifier(namespace)
10181019
namespace_concat = self._encode_namespace_path(namespace_tuple)
1019-
response = self._session.get(self.url(Endpoints.list_tables, namespace=namespace_concat))
1020-
try:
1021-
response.raise_for_status()
1022-
except HTTPError as exc:
1023-
_handle_non_200_response(exc, {404: NoSuchNamespaceError})
1024-
return [(*table.namespace, table.name) for table in ListTablesResponse.model_validate_json(response.text).identifiers]
1020+
1021+
all_identifiers: list[Identifier] = []
1022+
page_token: str | None = None
1023+
1024+
while True:
1025+
# Build URL with pagination params
1026+
url = self.url(Endpoints.list_tables, namespace=namespace_concat)
1027+
if page_token:
1028+
url = f"{url}?pageToken={page_token}"
1029+
1030+
response = self._session.get(url)
1031+
try:
1032+
response.raise_for_status()
1033+
except HTTPError as exc:
1034+
_handle_non_200_response(exc, {404: NoSuchNamespaceError})
1035+
1036+
parsed = ListTablesResponse.model_validate_json(response.text)
1037+
all_identifiers.extend([(*table.namespace, table.name) for table in parsed.identifiers])
1038+
1039+
# Check if more pages exist
1040+
if not parsed.next_page_token:
1041+
break
1042+
page_token = parsed.next_page_token
1043+
1044+
return all_identifiers
10251045

10261046
@retry(**_RETRY_ARGS)
10271047
def load_table(self, identifier: str | Identifier) -> Table:

tests/catalog/test_rest.py

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

479479

480+
def test_list_tables_paginated_200(rest_mock: Mocker) -> None:
481+
namespace = "examples"
482+
# First page with next-page-token
483+
rest_mock.get(
484+
f"{TEST_URI}v1/namespaces/{namespace}/tables",
485+
json={
486+
"identifiers": [
487+
{"namespace": ["examples"], "name": "table1"},
488+
{"namespace": ["examples"], "name": "table2"},
489+
],
490+
"next-page-token": "page2token",
491+
},
492+
status_code=200,
493+
request_headers=TEST_HEADERS,
494+
)
495+
# Second page with next-page-token
496+
rest_mock.get(
497+
f"{TEST_URI}v1/namespaces/{namespace}/tables?pageToken=page2token",
498+
json={
499+
"identifiers": [
500+
{"namespace": ["examples"], "name": "table3"},
501+
],
502+
"next-page-token": "page3token",
503+
},
504+
status_code=200,
505+
request_headers=TEST_HEADERS,
506+
)
507+
# Third page without next-page-token (last page)
508+
rest_mock.get(
509+
f"{TEST_URI}v1/namespaces/{namespace}/tables?pageToken=page3token",
510+
json={
511+
"identifiers": [
512+
{"namespace": ["examples"], "name": "table4"},
513+
],
514+
},
515+
status_code=200,
516+
request_headers=TEST_HEADERS,
517+
)
518+
519+
result = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).list_tables(namespace)
520+
assert result == [
521+
("examples", "table1"),
522+
("examples", "table2"),
523+
("examples", "table3"),
524+
("examples", "table4"),
525+
]
526+
527+
480528
def test_list_tables_200_sigv4(rest_mock: Mocker) -> None:
481529
namespace = "examples"
482530
rest_mock.get(

0 commit comments

Comments
 (0)