Skip to content

Commit ce7260d

Browse files
committed
Add pagination support for list_namespaces
1 parent d339391 commit ce7260d

2 files changed

Lines changed: 68 additions & 11 deletions

File tree

pyiceberg/catalog/rest/__init__.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ def _parse_endpoints(cls, v: list[str] | None) -> set[Endpoint] | None:
350350

351351
class ListNamespaceResponse(IcebergBaseModel):
352352
namespaces: list[Identifier] = Field()
353+
next_page_token: str | None = Field(default=None, alias="next-page-token")
353354

354355

355356
class NamespaceResponse(IcebergBaseModel):
@@ -1243,19 +1244,31 @@ def drop_namespace(self, namespace: str | Identifier) -> None:
12431244
def list_namespaces(self, namespace: str | Identifier = ()) -> list[Identifier]:
12441245
self._check_endpoint(Capability.V1_LIST_NAMESPACES)
12451246
namespace_tuple = self.identifier_to_tuple(namespace)
1246-
response = self._session.get(
1247-
self.url(
1248-
f"{Endpoints.list_namespaces}?parent={self._encode_namespace_path(namespace_tuple)}"
1249-
if namespace_tuple
1250-
else Endpoints.list_namespaces
1251-
),
1247+
url = (
1248+
self.url(f"{Endpoints.list_namespaces}?parent={self._encode_namespace_path(namespace_tuple)}")
1249+
if namespace_tuple
1250+
else self.url(Endpoints.list_namespaces)
12521251
)
1253-
try:
1254-
response.raise_for_status()
1255-
except HTTPError as exc:
1256-
_handle_non_200_response(exc, {404: NoSuchNamespaceError})
12571252

1258-
return ListNamespaceResponse.model_validate_json(response.text).namespaces
1253+
namespaces: list[Identifier] = []
1254+
page_token: str | None = None
1255+
1256+
while True:
1257+
params = {"pageToken": page_token} if page_token else None
1258+
response = self._session.get(url, params=params)
1259+
try:
1260+
response.raise_for_status()
1261+
except HTTPError as exc:
1262+
_handle_non_200_response(exc, {404: NoSuchNamespaceError})
1263+
1264+
parsed = ListNamespaceResponse.model_validate_json(response.text)
1265+
namespaces.extend(parsed.namespaces)
1266+
1267+
if not parsed.next_page_token:
1268+
break
1269+
page_token = parsed.next_page_token
1270+
1271+
return namespaces
12591272

12601273
@retry(**_RETRY_ARGS)
12611274
@override

tests/catalog/test_rest.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,50 @@ def test_list_namespace_with_parent_404(rest_mock: Mocker) -> None:
841841
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).list_namespaces(("some_namespace",))
842842

843843

844+
def test_list_namespaces_paginated_200(rest_mock: Mocker) -> None:
845+
rest_mock.get(
846+
f"{TEST_URI}v1/namespaces",
847+
json={"namespaces": [["default"], ["examples"]], "next-page-token": "page2token"},
848+
status_code=200,
849+
request_headers=TEST_HEADERS,
850+
)
851+
rest_mock.get(
852+
f"{TEST_URI}v1/namespaces?pageToken=page2token",
853+
json={"namespaces": [["fokko"], ["system"]]},
854+
status_code=200,
855+
request_headers=TEST_HEADERS,
856+
)
857+
858+
assert RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).list_namespaces() == [
859+
("default",),
860+
("examples",),
861+
("fokko",),
862+
("system",),
863+
]
864+
865+
866+
def test_list_namespaces_paginated_200_none_next_page_token(rest_mock: Mocker) -> None:
867+
rest_mock.get(
868+
f"{TEST_URI}v1/namespaces",
869+
json={"namespaces": [["default"], ["examples"]], "next-page-token": "page2token"},
870+
status_code=200,
871+
request_headers=TEST_HEADERS,
872+
)
873+
rest_mock.get(
874+
f"{TEST_URI}v1/namespaces?pageToken=page2token",
875+
json={"namespaces": [["fokko"], ["system"]], "next-page-token": None},
876+
status_code=200,
877+
request_headers=TEST_HEADERS,
878+
)
879+
880+
assert RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).list_namespaces() == [
881+
("default",),
882+
("examples",),
883+
("fokko",),
884+
("system",),
885+
]
886+
887+
844888
@pytest.mark.filterwarnings(
845889
"ignore:Deprecated in 0.8.0, will be removed in 1.0.0. "
846890
"Iceberg REST client is missing the OAuth2 server URI:DeprecationWarning"

0 commit comments

Comments
 (0)