This is an automated email from the ASF dual-hosted git repository.
kevinjqliu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git
The following commit(s) were added to refs/heads/main by this push:
new 8055a60af REST: Add support for page-size in list_namespaces,
list_tables, and list_views (#3377)
8055a60af is described below
commit 8055a60af5d32ec4d2e1d02cd020a78273785558
Author: Yuya Ebihara <[email protected]>
AuthorDate: Sun May 24 12:04:50 2026 +0900
REST: Add support for page-size in list_namespaces, list_tables, and
list_views (#3377)
<!--
Thanks for opening a pull request!
-->
<!-- In the case this PR will resolve an issue, please replace
${GITHUB_ISSUE_ID} below with the actual Github issue id. -->
<!-- Closes #${GITHUB_ISSUE_ID} -->
# Rationale for this change
Follows REST catalog spec:
* `/v1/{prefix}/views`:
https://github.com/apache/iceberg/blob/e7a5a87f26f9de5b200254155aa037368b13a29c/open-api/rest-catalog-open-api.yaml#L1525-L1538
* `page-size`:
https://github.com/apache/iceberg/blob/e7a5a87f26f9de5b200254155aa037368b13a29c/open-api/rest-catalog-open-api.yaml#L2029-L2038
## Are these changes tested?
Yes.
## Are there any user-facing changes?
Add support for `page-size` in list_views in REST catalog.
<!-- In the case of user-facing changes, please add the changelog label.
-->
---
pyiceberg/catalog/rest/__init__.py | 29 +++++++++++--
tests/catalog/test_rest.py | 85 ++++++++++++++++++++++++++++++++++++++
2 files changed, 111 insertions(+), 3 deletions(-)
diff --git a/pyiceberg/catalog/rest/__init__.py
b/pyiceberg/catalog/rest/__init__.py
index 4db3b7920..7a09e376f 100644
--- a/pyiceberg/catalog/rest/__init__.py
+++ b/pyiceberg/catalog/rest/__init__.py
@@ -267,6 +267,8 @@ SCAN_PLANNING_MODE_DEFAULT = ScanPlanningMode.CLIENT.value
VIEW_ENDPOINTS_SUPPORTED = "view-endpoints-supported"
VIEW_ENDPOINTS_SUPPORTED_DEFAULT = False
+PAGE_SIZE = "rest-page-size"
+
NAMESPACE_SEPARATOR_PROPERTY = "namespace-separator"
DEFAULT_NAMESPACE_SEPARATOR = b"\x1f".decode(UTF8)
@@ -1042,11 +1044,17 @@ class RestCatalog(Catalog):
namespace_concat = self._encode_namespace_path(namespace_tuple)
url = self.url(Endpoints.list_tables, namespace=namespace_concat)
+ params: dict[str, str] = {}
+ page_size = property_as_int(self.properties, PAGE_SIZE, None)
+ if page_size is not None:
+ if page_size <= 0:
+ raise ValueError(f"{PAGE_SIZE} must be a positive integer")
+ params["pageSize"] = str(page_size)
+
tables: list[Identifier] = []
page_token: str | None = None
while True:
- params: dict[str, str] = {}
if page_token:
params["pageToken"] = page_token
response = self._session.get(url, params=params)
@@ -1150,11 +1158,20 @@ class RestCatalog(Catalog):
namespace_concat = self._encode_namespace_path(namespace_tuple)
url = self.url(Endpoints.list_views, namespace=namespace_concat)
+ params: dict[str, str] = {}
+ page_size = property_as_int(self.properties, PAGE_SIZE, None)
+ if page_size is not None:
+ if page_size <= 0:
+ raise ValueError(f"{PAGE_SIZE} must be a positive integer")
+ params["pageSize"] = str(page_size)
+
views: list[Identifier] = []
page_token: str | None = None
while True:
- params = {"pageToken": page_token} if page_token else None
+ if page_token:
+ params["pageToken"] = page_token
+
response = self._session.get(url, params=params)
try:
response.raise_for_status()
@@ -1263,11 +1280,17 @@ class RestCatalog(Catalog):
self._check_endpoint(Capability.V1_LIST_NAMESPACES)
namespace_tuple = self.identifier_to_tuple(namespace)
+ params: dict[str, str] = {}
+ page_size = property_as_int(self.properties, PAGE_SIZE, None)
+ if page_size is not None:
+ if page_size <= 0:
+ raise ValueError(f"{PAGE_SIZE} must be a positive integer")
+ params["pageSize"] = str(page_size)
+
namespaces: list[Identifier] = []
page_token: str | None = None
while True:
- params: dict[str, str] = {}
if namespace_tuple:
params["parent"] = self._encode_namespace_path(namespace_tuple)
if page_token:
diff --git a/tests/catalog/test_rest.py b/tests/catalog/test_rest.py
index 857cfb28a..1eb9f26a5 100644
--- a/tests/catalog/test_rest.py
+++ b/tests/catalog/test_rest.py
@@ -35,6 +35,7 @@ from pyiceberg.catalog.rest import (
DEFAULT_ENDPOINTS,
EMPTY_BODY_SHA256,
OAUTH2_SERVER_URI,
+ PAGE_SIZE,
SIGV4_MAX_RETRIES,
SIGV4_MAX_RETRIES_DEFAULT,
SNAPSHOT_LOADING_MODE,
@@ -564,6 +565,29 @@ def
test_list_tables_paginated_200_none_next_page_token(rest_mock: Mocker) -> No
]
+def test_list_tables_page_size(rest_mock: Mocker) -> None:
+ namespace = "examples"
+ rest_mock.get(
+ f"{TEST_URI}v1/namespaces/{namespace}/tables",
+ json={
+ "identifiers": [
+ {"namespace": ["examples"], "name": "table1"},
+ {"namespace": ["examples"], "name": "table2"},
+ ],
+ },
+ status_code=200,
+ request_headers=TEST_HEADERS,
+ )
+
+ result = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN, **{PAGE_SIZE:
"100"}).list_tables(namespace)
+ assert rest_mock.last_request.url ==
f"{TEST_URI}v1/namespaces/examples/tables?pageSize=100"
+
+ assert result == [
+ ("examples", "table1"),
+ ("examples", "table2"),
+ ]
+
+
def test_list_tables_200_sigv4(rest_mock: Mocker) -> None:
namespace = "examples"
rest_mock.get(
@@ -810,6 +834,48 @@ def
test_list_views_paginated_200_none_next_page_token(rest_mock: Mocker) -> Non
]
+def test_list_views_page_size(rest_mock: Mocker) -> None:
+ namespace = "examples"
+ rest_mock.get(
+ f"{TEST_URI}v1/namespaces/{namespace}/views",
+ json={
+ "identifiers": [
+ {"namespace": ["examples"], "name": "view1"},
+ {"namespace": ["examples"], "name": "view2"},
+ ],
+ },
+ status_code=200,
+ request_headers=TEST_HEADERS,
+ )
+
+ result = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN, **{PAGE_SIZE:
"100"}).list_views(namespace)
+ assert rest_mock.last_request.url ==
f"{TEST_URI}v1/namespaces/examples/views?pageSize=100"
+
+ assert result == [
+ ("examples", "view1"),
+ ("examples", "view2"),
+ ]
+
+
+def test_list_views_invalid_page_size(rest_mock: Mocker) -> None:
+ namespace = "examples"
+ rest_mock.get(
+ f"{TEST_URI}v1/namespaces/{namespace}/views",
+ json={
+ "identifiers": [
+ {"namespace": ["examples"], "name": "view1"},
+ {"namespace": ["examples"], "name": "view2"},
+ ],
+ },
+ status_code=200,
+ request_headers=TEST_HEADERS,
+ )
+
+ with pytest.raises(ValueError) as e:
+ RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN, **{PAGE_SIZE:
"0"}).list_views(namespace)
+ assert str(e.value) == "rest-page-size must be a positive integer"
+
+
def test_list_views_200_sigv4(rest_mock: Mocker) -> None:
namespace = "examples"
rest_mock.get(
@@ -1006,6 +1072,25 @@ def
test_list_namespaces_paginated_200_none_next_page_token(rest_mock: Mocker) -
]
+def test_list_namespaces_page_size(rest_mock: Mocker) -> None:
+ rest_mock.get(
+ f"{TEST_URI}v1/namespaces",
+ json={
+ "namespaces": [["ns1"], ["ns2"]],
+ },
+ status_code=200,
+ request_headers=TEST_HEADERS,
+ )
+
+ result = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN, **{PAGE_SIZE:
"100"}).list_namespaces()
+ assert rest_mock.last_request.url ==
f"{TEST_URI}v1/namespaces?pageSize=100"
+
+ assert result == [
+ ("ns1",),
+ ("ns2",),
+ ]
+
+
def test_list_namespace_with_parent_404(rest_mock: Mocker) -> None:
rest_mock.get(
f"{TEST_URI}v1/namespaces?parent=some_namespace",