From d68db281bfdca7e1d0e298053c2f867110ef54f5 Mon Sep 17 00:00:00 2001 From: Soumo-git-hub Date: Thu, 8 Oct 2026 01:15:50 +0530 Subject: [PATCH 1/3] REST: preserve view metadata location --- pyiceberg/catalog/rest/__init__.py | 1 + pyiceberg/view/__init__.py | 3 +++ tests/catalog/test_rest.py | 3 +++ 3 files changed, 7 insertions(+) diff --git a/pyiceberg/catalog/rest/__init__.py b/pyiceberg/catalog/rest/__init__.py index 2b8b4496f8..97b2e23505 100644 --- a/pyiceberg/catalog/rest/__init__.py +++ b/pyiceberg/catalog/rest/__init__.py @@ -1187,6 +1187,7 @@ def _response_to_view(self, identifier_tuple: tuple[str, ...], view_response: Vi identifier=identifier_tuple, metadata=view_response.metadata, config=view_response.config, + metadata_location=view_response.metadata_location, ) def _refresh_token(self) -> None: diff --git a/pyiceberg/view/__init__.py b/pyiceberg/view/__init__.py index 4f298e5b60..1ab099a863 100644 --- a/pyiceberg/view/__init__.py +++ b/pyiceberg/view/__init__.py @@ -30,16 +30,19 @@ class View: _identifier: Identifier metadata: ViewMetadata config: dict[str, str] + metadata_location: str | None def __init__( self, identifier: Identifier, metadata: ViewMetadata, config: dict[str, str] = EMPTY_DICT, + metadata_location: str | None = None, ) -> None: self._identifier = identifier self.metadata = metadata self.config = config + self.metadata_location = metadata_location 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 4877ee2f73..32d5a6e983 100644 --- a/tests/catalog/test_rest.py +++ b/tests/catalog/test_rest.py @@ -1802,6 +1802,7 @@ def test_create_view_200(rest_mock: Mocker, table_schema_simple: Schema, example ) assert actual == expected assert actual.config == example_view_metadata_rest_json["config"] + assert actual.metadata_location == example_view_metadata_rest_json["metadata-location"] def test_create_view_409( @@ -1849,6 +1850,7 @@ def test_load_view_200(rest_mock: Mocker, example_view_metadata_rest_json: dict[ expected = View(identifier=("fokko", "view"), metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"])) assert actual == expected assert actual.config == example_view_metadata_rest_json["config"] + assert actual.metadata_location == example_view_metadata_rest_json["metadata-location"] def test_load_view_404(rest_mock: Mocker) -> None: @@ -2809,6 +2811,7 @@ def test_register_view_200(rest_mock: Mocker, example_view_metadata_rest_json: d ) assert actual == expected assert actual.config == example_view_metadata_rest_json["config"] + assert actual.metadata_location == example_view_metadata_rest_json["metadata-location"] def test_register_view_409_view(rest_mock: Mocker) -> None: From 586628558e5e3e73c0cfe6fc3c389b9c9e6e59e1 Mon Sep 17 00:00:00 2001 From: Soumo-git-hub Date: Sun, 11 Oct 2026 12:54:47 +0530 Subject: [PATCH 2/3] View: clarify metadata location semantics --- pyiceberg/view/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyiceberg/view/__init__.py b/pyiceberg/view/__init__.py index 1ab099a863..e2f7bb97d4 100644 --- a/pyiceberg/view/__init__.py +++ b/pyiceberg/view/__init__.py @@ -30,6 +30,7 @@ class View: _identifier: Identifier metadata: ViewMetadata config: dict[str, str] + # None when no metadata location is available, such as for directly constructed views. metadata_location: str | None def __init__( @@ -95,4 +96,5 @@ def sql_for(self, dialect: str) -> SQLViewRepresentation | None: def __eq__(self, other: Any) -> bool: """Return the equality of two instances of the View class.""" + # Metadata location is intentionally excluded from view equality. return self.name() == other.name() and self.metadata == other.metadata if isinstance(other, View) else False From 8592d7dd745396926219877494b32d210a5341f0 Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Sat, 24 Oct 2026 17:57:03 -0700 Subject: [PATCH 3/3] View: include metadata location in equality Match Table.__eq__ and the REST spec, which uses metadata location to detect view changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- pyiceberg/view/__init__.py | 7 +++++-- tests/catalog/test_rest.py | 11 +++++++---- tests/integration/test_rest_catalog.py | 1 + 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/pyiceberg/view/__init__.py b/pyiceberg/view/__init__.py index e2f7bb97d4..1f7c0e906b 100644 --- a/pyiceberg/view/__init__.py +++ b/pyiceberg/view/__init__.py @@ -96,5 +96,8 @@ def sql_for(self, dialect: str) -> SQLViewRepresentation | None: def __eq__(self, other: Any) -> bool: """Return the equality of two instances of the View class.""" - # Metadata location is intentionally excluded from view equality. - return self.name() == other.name() and self.metadata == other.metadata if isinstance(other, View) else False + return ( + self.name() == other.name() and self.metadata == other.metadata and self.metadata_location == other.metadata_location + if isinstance(other, View) + else False + ) diff --git a/tests/catalog/test_rest.py b/tests/catalog/test_rest.py index 32d5a6e983..b1b46e4673 100644 --- a/tests/catalog/test_rest.py +++ b/tests/catalog/test_rest.py @@ -1799,10 +1799,10 @@ def test_create_view_200(rest_mock: Mocker, table_schema_simple: Schema, example expected = View( identifier=("fokko", "fokko2"), metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"]), + metadata_location=example_view_metadata_rest_json["metadata-location"], ) assert actual == expected assert actual.config == example_view_metadata_rest_json["config"] - assert actual.metadata_location == example_view_metadata_rest_json["metadata-location"] def test_create_view_409( @@ -1847,10 +1847,13 @@ def test_load_view_200(rest_mock: Mocker, example_view_metadata_rest_json: dict[ ) catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN) actual = catalog.load_view(("fokko", "view")) - expected = View(identifier=("fokko", "view"), metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"])) + expected = View( + identifier=("fokko", "view"), + metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"]), + metadata_location=example_view_metadata_rest_json["metadata-location"], + ) assert actual == expected assert actual.config == example_view_metadata_rest_json["config"] - assert actual.metadata_location == example_view_metadata_rest_json["metadata-location"] def test_load_view_404(rest_mock: Mocker) -> None: @@ -2808,10 +2811,10 @@ def test_register_view_200(rest_mock: Mocker, example_view_metadata_rest_json: d expected = View( identifier=("default", "registered_view"), metadata=ViewMetadata(**example_view_metadata_rest_json["metadata"]), + metadata_location=example_view_metadata_rest_json["metadata-location"], ) assert actual == expected assert actual.config == example_view_metadata_rest_json["config"] - assert actual.metadata_location == example_view_metadata_rest_json["metadata-location"] def test_register_view_409_view(rest_mock: Mocker) -> None: diff --git a/tests/integration/test_rest_catalog.py b/tests/integration/test_rest_catalog.py index 05039a982e..1705dcfb27 100644 --- a/tests/integration/test_rest_catalog.py +++ b/tests/integration/test_rest_catalog.py @@ -92,6 +92,7 @@ def test_load_view(catalog: RestCatalog, table_schema_nested: Schema, database_n ) view = catalog.create_view(identifier, table_schema_nested, view_version=view_version) loaded_view = catalog.load_view(identifier) + assert view.metadata_location is not None assert view == loaded_view