From c0349cb277ffb77006ccda9e24a2daae241679bd Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Sun, 13 Sep 2026 15:56:06 -0700 Subject: [PATCH 1/2] Preserve REST view response configuration Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- mkdocs/docs/api.md | 2 ++ pyiceberg/catalog/rest/__init__.py | 1 + pyiceberg/view/__init__.py | 5 ++++- tests/catalog/test_rest.py | 8 +++++++- tests/test_view.py | 13 +++++++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/mkdocs/docs/api.md b/mkdocs/docs/api.md index 1f5d4d6692..ac481ec274 100644 --- a/mkdocs/docs/api.md +++ b/mkdocs/docs/api.md @@ -1727,6 +1727,8 @@ sql_representation = view.sql_for("spark") print(sql_representation.sql) ``` +Views returned by the REST catalog's create, load, and register operations expose response configuration as `view.config`, defaulting to an empty dictionary when omitted. This transient configuration is separate from `view.properties`, which contains persistent view metadata properties. + ## Check if a view exists To check whether the `some_view` view exists: diff --git a/pyiceberg/catalog/rest/__init__.py b/pyiceberg/catalog/rest/__init__.py index d8f58773e6..e7213d2500 100644 --- a/pyiceberg/catalog/rest/__init__.py +++ b/pyiceberg/catalog/rest/__init__.py @@ -1179,6 +1179,7 @@ def _response_to_view(self, identifier_tuple: tuple[str, ...], view_response: Vi return View( identifier=identifier_tuple, metadata=view_response.metadata, + config=view_response.config, ) def _refresh_token(self) -> None: diff --git a/pyiceberg/view/__init__.py b/pyiceberg/view/__init__.py index e69b03e4e8..4f298e5b60 100644 --- a/pyiceberg/view/__init__.py +++ b/pyiceberg/view/__init__.py @@ -20,7 +20,7 @@ from uuid import UUID from pyiceberg.schema import Schema -from pyiceberg.typedef import Identifier +from pyiceberg.typedef import EMPTY_DICT, Identifier from pyiceberg.view.metadata import SQLViewRepresentation, ViewHistoryEntry, ViewMetadata, ViewVersion @@ -29,14 +29,17 @@ class View: _identifier: Identifier metadata: ViewMetadata + config: dict[str, str] def __init__( self, identifier: Identifier, metadata: ViewMetadata, + config: dict[str, str] = EMPTY_DICT, ) -> None: self._identifier = identifier self.metadata = metadata + self.config = config def name(self) -> Identifier: """Return the identifier of this view.""" diff --git a/tests/catalog/test_rest.py b/tests/catalog/test_rest.py index a918829c24..d3f84a49d4 100644 --- a/tests/catalog/test_rest.py +++ b/tests/catalog/test_rest.py @@ -1764,6 +1764,7 @@ def test_create_view_200(rest_mock: Mocker, table_schema_simple: Schema, example metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"]), ) assert actual == expected + assert actual.config == example_view_metadata_rest_json["config"] def test_create_view_409( @@ -1799,7 +1800,10 @@ def test_create_view_409( assert "View already exists" in str(e.value) -def test_load_view_200(rest_mock: Mocker, example_view_metadata_rest_json: dict[str, Any]) -> None: +@pytest.mark.parametrize("include_config", [True, False]) +def test_load_view_200(rest_mock: Mocker, example_view_metadata_rest_json: dict[str, Any], include_config: bool) -> None: + if not include_config: + del example_view_metadata_rest_json["config"] rest_mock.get( f"{TEST_URI}v1/namespaces/fokko/views/view", json=example_view_metadata_rest_json, @@ -1810,6 +1814,7 @@ def test_load_view_200(rest_mock: Mocker, example_view_metadata_rest_json: dict[ actual = catalog.load_view(("fokko", "view")) expected = View(identifier=("fokko", "view"), metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"])) assert actual == expected + assert actual.config == example_view_metadata_rest_json.get("config", {}) def test_load_view_404(rest_mock: Mocker) -> None: @@ -2769,6 +2774,7 @@ def test_register_view_200(rest_mock: Mocker, example_view_metadata_rest_json: d metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"]), ) assert actual == expected + assert actual.config == example_view_metadata_rest_json["config"] def test_register_view_409_view(rest_mock: Mocker) -> None: diff --git a/tests/test_view.py b/tests/test_view.py index 00f8f24bbf..5451fb56c7 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -81,6 +81,19 @@ def test_view_properties(view: View) -> None: assert view.properties == {"comment": "this is a test view"} +def test_view_config_defaults_to_empty(view: View) -> None: + assert view.config == {} + + +def test_view_config(view: View) -> None: + config = {"token": "view-token"} + configured_view = View(view.name(), view.metadata, config=config) + + assert configured_view.config == config + assert configured_view.properties == view.properties + assert configured_view == view + + def test_view_location(view: View) -> None: assert view.location() == "s3://bucket/test/location/test_view" From d40a9e299f09afd515de71c1e5d411ad1d4aee1a Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Sun, 13 Sep 2026 16:29:04 -0700 Subject: [PATCH 2/2] Keep view configuration prerequisite focused Remove the API documentation addition and keep the REST load test focused on preserving nonempty configuration. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- mkdocs/docs/api.md | 2 -- tests/catalog/test_rest.py | 7 ++----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/mkdocs/docs/api.md b/mkdocs/docs/api.md index ac481ec274..1f5d4d6692 100644 --- a/mkdocs/docs/api.md +++ b/mkdocs/docs/api.md @@ -1727,8 +1727,6 @@ sql_representation = view.sql_for("spark") print(sql_representation.sql) ``` -Views returned by the REST catalog's create, load, and register operations expose response configuration as `view.config`, defaulting to an empty dictionary when omitted. This transient configuration is separate from `view.properties`, which contains persistent view metadata properties. - ## Check if a view exists To check whether the `some_view` view exists: diff --git a/tests/catalog/test_rest.py b/tests/catalog/test_rest.py index d3f84a49d4..4d1a2b2a79 100644 --- a/tests/catalog/test_rest.py +++ b/tests/catalog/test_rest.py @@ -1800,10 +1800,7 @@ def test_create_view_409( assert "View already exists" in str(e.value) -@pytest.mark.parametrize("include_config", [True, False]) -def test_load_view_200(rest_mock: Mocker, example_view_metadata_rest_json: dict[str, Any], include_config: bool) -> None: - if not include_config: - del example_view_metadata_rest_json["config"] +def test_load_view_200(rest_mock: Mocker, example_view_metadata_rest_json: dict[str, Any]) -> None: rest_mock.get( f"{TEST_URI}v1/namespaces/fokko/views/view", json=example_view_metadata_rest_json, @@ -1814,7 +1811,7 @@ def test_load_view_200(rest_mock: Mocker, example_view_metadata_rest_json: dict[ actual = catalog.load_view(("fokko", "view")) expected = View(identifier=("fokko", "view"), metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"])) assert actual == expected - assert actual.config == example_view_metadata_rest_json.get("config", {}) + assert actual.config == example_view_metadata_rest_json["config"] def test_load_view_404(rest_mock: Mocker) -> None: