Skip to content

Commit caa06a7

Browse files
committed
just need to add abstract methods
1 parent df258f5 commit caa06a7

3 files changed

Lines changed: 92 additions & 0 deletions

File tree

pyiceberg/catalog/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
NamespaceAlreadyExistsError,
3636
NoSuchNamespaceError,
3737
NoSuchTableError,
38+
NoSuchViewError,
3839
NotInstalledError,
3940
TableAlreadyExistsError,
41+
ViewAlreadyExistsError,
4042
)
4143
from pyiceberg.io import FileIO, load_file_io
4244
from pyiceberg.manifest import ManifestFile
@@ -710,6 +712,18 @@ def create_view(
710712
ViewAlreadyExistsError: If a view with the name already exists.
711713
"""
712714

715+
@abstractmethod
716+
def rename_view(self, from_identifier: str | Identifier, to_identifier: str | Identifier) -> None:
717+
"""Rename a fully classified view name.
718+
719+
Args:
720+
from_identifier (str | Identifier): Existing view identifier.
721+
to_identifier (str | Identifier): New view identifier.
722+
723+
Raises:
724+
NoSuchViewError: If a view with the name does not exist.
725+
"""
726+
713727
@staticmethod
714728
def identifier_to_tuple(identifier: str | Identifier) -> Identifier:
715729
"""Parse an identifier to a tuple.

pyiceberg/catalog/rest/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ class Endpoints:
155155
create_view: str = "namespaces/{namespace}/views"
156156
drop_view: str = "namespaces/{namespace}/views/{view}"
157157
view_exists: str = "namespaces/{namespace}/views/{view}"
158+
rename_view: str = "views/rename"
158159
plan_table_scan: str = "namespaces/{namespace}/tables/{table}/plan"
159160
fetch_scan_tasks: str = "namespaces/{namespace}/tables/{table}/tasks"
160161

@@ -182,6 +183,7 @@ class Capability:
182183
V1_LIST_VIEWS = Endpoint(http_method=HttpMethod.GET, path=f"{API_PREFIX}/{Endpoints.list_views}")
183184
V1_VIEW_EXISTS = Endpoint(http_method=HttpMethod.HEAD, path=f"{API_PREFIX}/{Endpoints.view_exists}")
184185
V1_DELETE_VIEW = Endpoint(http_method=HttpMethod.DELETE, path=f"{API_PREFIX}/{Endpoints.drop_view}")
186+
V1_RENAME_VIEW = Endpoint(http_method=HttpMethod.POST, path=f"{API_PREFIX}/{Endpoints.rename_view}")
185187
V1_SUBMIT_TABLE_SCAN_PLAN = Endpoint(http_method=HttpMethod.POST, path=f"{API_PREFIX}/{Endpoints.plan_table_scan}")
186188
V1_TABLE_SCAN_PLAN_TASKS = Endpoint(http_method=HttpMethod.POST, path=f"{API_PREFIX}/{Endpoints.fetch_scan_tasks}")
187189

@@ -210,6 +212,7 @@ class Capability:
210212
(
211213
Capability.V1_LIST_VIEWS,
212214
Capability.V1_DELETE_VIEW,
215+
Capability.V1_RENAME_VIEW,
213216
)
214217
)
215218

@@ -1323,6 +1326,18 @@ def drop_view(self, identifier: str) -> None:
13231326
except HTTPError as exc:
13241327
_handle_non_200_response(exc, {404: NoSuchViewError})
13251328

1329+
@retry(**_RETRY_ARGS)
1330+
def rename_view(self, from_identifier: Union[str, Identifier], to_identifier: Union[str, Identifier]) -> None:
1331+
payload = {
1332+
"source": self._split_identifier_for_json(from_identifier),
1333+
"destination": self._split_identifier_for_json(to_identifier),
1334+
}
1335+
response = self._session.post(self.url(Endpoints.rename_view), json=payload)
1336+
try:
1337+
response.raise_for_status()
1338+
except HTTPError as exc:
1339+
_handle_non_200_response(exc, {404: NoSuchViewError, 409: ViewAlreadyExistsError})
1340+
13261341
def close(self) -> None:
13271342
"""Close the catalog and release Session connection adapters.
13281343

tests/catalog/test_rest.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2325,6 +2325,7 @@ def test_rest_catalog_context_manager_with_exception_sigv4(self, rest_mock: Mock
23252325
assert catalog is not None and hasattr(catalog, "_session")
23262326
assert len(catalog._session.adapters) == self.EXPECTED_ADAPTERS_SIGV4
23272327

2328+
<<<<<<< HEAD
23282329
def test_server_side_planning_disabled_by_default(self, rest_mock: Mocker) -> None:
23292330
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
23302331

@@ -2654,3 +2655,65 @@ def test_load_table_without_storage_credentials(
26542655
)
26552656
assert actual.metadata.model_dump() == expected.metadata.model_dump()
26562657
assert actual == expected
2658+
2659+
2660+
def test_rename_view_204(rest_mock: Mocker) -> None:
2661+
from_identifier = ("some_namespace", "old_view")
2662+
to_identifier = ("some_namespace", "new_view")
2663+
rest_mock.post(
2664+
f"{TEST_URI}v1/views/rename",
2665+
json={
2666+
"source": {"namespace": ["some_namespace"], "name": "old_view"},
2667+
"destination": {"namespace": ["some_namespace"], "name": "new_view"},
2668+
},
2669+
status_code=204,
2670+
request_headers=TEST_HEADERS,
2671+
)
2672+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
2673+
catalog.rename_view(from_identifier, to_identifier)
2674+
assert (
2675+
rest_mock.last_request.text
2676+
== """{"source": {"namespace": ["some_namespace"], "name": "old_view"}, "destination": {"namespace": ["some_namespace"], "name": "new_view"}}"""
2677+
)
2678+
2679+
2680+
def test_rename_view_404(rest_mock: Mocker) -> None:
2681+
from_identifier = ("some_namespace", "non_existent_view")
2682+
to_identifier = ("some_namespace", "new_view")
2683+
rest_mock.post(
2684+
f"{TEST_URI}v1/views/rename",
2685+
json={
2686+
"error": {
2687+
"message": "View does not exist: some_namespace.non_existent_view",
2688+
"type": "NoSuchViewException",
2689+
"code": 404,
2690+
}
2691+
},
2692+
status_code=404,
2693+
request_headers=TEST_HEADERS,
2694+
)
2695+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
2696+
with pytest.raises(NoSuchViewError) as exc_info:
2697+
catalog.rename_view(from_identifier, to_identifier)
2698+
assert "View does not exist: some_namespace.non_existent_view" in str(exc_info.value)
2699+
2700+
2701+
def test_rename_view_409(rest_mock: Mocker) -> None:
2702+
from_identifier = ("some_namespace", "old_view")
2703+
to_identifier = ("some_namespace", "existing_view")
2704+
rest_mock.post(
2705+
f"{TEST_URI}v1/views/rename",
2706+
json={
2707+
"error": {
2708+
"message": "View already exists: some_namespace.existing_view",
2709+
"type": "ViewAlreadyExistsException",
2710+
"code": 409,
2711+
}
2712+
},
2713+
status_code=409,
2714+
request_headers=TEST_HEADERS,
2715+
)
2716+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
2717+
with pytest.raises(ViewAlreadyExistsError) as exc_info:
2718+
catalog.rename_view(from_identifier, to_identifier)
2719+
assert "View already exists: some_namespace.existing_view" in str(exc_info.value)

0 commit comments

Comments
 (0)