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 ccb3a29b feat: Add RESTscan Planning Endpoints and config (#2842)
ccb3a29b is described below
commit ccb3a29bfa98b59cfeb6fec7409f52162a09b2b7
Author: Drew Gallardo <[email protected]>
AuthorDate: Fri Dec 19 16:00:35 2025 -0800
feat: Add RESTscan Planning Endpoints and config (#2842)
related to #2775
# Rationale for this change
This PR adds some of the rest scanning endpoints needed for the
synchronous scan planning And the configuration property used to
determine scan planning support.
Aligning with the java implementation in
https://github.com/apache/iceberg/pull/13400.
## Usage
```
catalog = RestCatalog(
"the-best-rest-catalog", uri="http://127.0.0.1:8181",
**{"rest.scan-planning.enabled": "true"},
)
```
Ideally we only would want to use rest scan planning if the catalog is
**_capable_**. I remember there was a PR open for this at one point but
can't seem to find it.
## Are these changes tested?
## Are there any user-facing changes?
new property to configure scanning
---
pyiceberg/catalog/rest/__init__.py | 12 +++++++++
tests/catalog/test_rest.py | 50 ++++++++++++++++++++++++++++++++++++++
2 files changed, 62 insertions(+)
diff --git a/pyiceberg/catalog/rest/__init__.py
b/pyiceberg/catalog/rest/__init__.py
index 3b77fd47..a28ff562 100644
--- a/pyiceberg/catalog/rest/__init__.py
+++ b/pyiceberg/catalog/rest/__init__.py
@@ -96,6 +96,8 @@ class Endpoints:
list_views: str = "namespaces/{namespace}/views"
drop_view: str = "namespaces/{namespace}/views/{view}"
view_exists: str = "namespaces/{namespace}/views/{view}"
+ plan_table_scan: str = "namespaces/{namespace}/tables/{table}/plan"
+ fetch_scan_tasks: str = "namespaces/{namespace}/tables/{table}/tasks"
class IdentifierKind(Enum):
@@ -130,6 +132,8 @@ OAUTH2_SERVER_URI = "oauth2-server-uri"
SNAPSHOT_LOADING_MODE = "snapshot-loading-mode"
AUTH = "auth"
CUSTOM = "custom"
+REST_SCAN_PLANNING_ENABLED = "rest-scan-planning-enabled"
+REST_SCAN_PLANNING_ENABLED_DEFAULT = False
NAMESPACE_SEPARATOR = b"\x1f".decode(UTF8)
@@ -269,6 +273,14 @@ class RestCatalog(Catalog):
return session
+ def is_rest_scan_planning_enabled(self) -> bool:
+ """Check if rest server-side scan planning is enabled.
+
+ Returns:
+ True if enabled, False otherwise.
+ """
+ return property_as_bool(self.properties, REST_SCAN_PLANNING_ENABLED,
REST_SCAN_PLANNING_ENABLED_DEFAULT)
+
def _create_legacy_oauth2_auth_manager(self, session: Session) ->
AuthManager:
"""Create the LegacyOAuth2AuthManager by fetching required properties.
diff --git a/tests/catalog/test_rest.py b/tests/catalog/test_rest.py
index b8bee002..464314f3 100644
--- a/tests/catalog/test_rest.py
+++ b/tests/catalog/test_rest.py
@@ -1993,3 +1993,53 @@ class TestRestCatalogClose:
assert catalog is not None and hasattr(catalog, "_session")
assert len(catalog._session.adapters) == self.EXPECTED_ADAPTERS_SIGV4
+
+ def test_rest_scan_planning_disabled_by_default(self, rest_mock: Mocker)
-> None:
+ rest_mock.get(
+ f"{TEST_URI}v1/config",
+ json={"defaults": {}, "overrides": {}},
+ status_code=200,
+ )
+ catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
+
+ assert catalog.is_rest_scan_planning_enabled() is False
+
+ def test_rest_scan_planning_enabled_by_property(self, rest_mock: Mocker)
-> None:
+ rest_mock.get(
+ f"{TEST_URI}v1/config",
+ json={"defaults": {}, "overrides": {}},
+ status_code=200,
+ )
+ catalog = RestCatalog(
+ "rest",
+ uri=TEST_URI,
+ token=TEST_TOKEN,
+ **{"rest-scan-planning-enabled": "true"},
+ )
+
+ assert catalog.is_rest_scan_planning_enabled() is True
+
+ def test_rest_scan_planning_explicitly_disabled(self, rest_mock: Mocker)
-> None:
+ rest_mock.get(
+ f"{TEST_URI}v1/config",
+ json={"defaults": {}, "overrides": {}},
+ status_code=200,
+ )
+ catalog = RestCatalog(
+ "rest",
+ uri=TEST_URI,
+ token=TEST_TOKEN,
+ **{"rest-scan-planning-enabled": "false"},
+ )
+
+ assert catalog.is_rest_scan_planning_enabled() is False
+
+ def test_rest_scan_planning_enabled_from_server_config(self, rest_mock:
Mocker) -> None:
+ rest_mock.get(
+ f"{TEST_URI}v1/config",
+ json={"defaults": {"rest-scan-planning-enabled": "true"},
"overrides": {}},
+ status_code=200,
+ )
+ catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
+
+ assert catalog.is_rest_scan_planning_enabled() is True