Skip to content

Commit 2d0869f

Browse files
committed
just need to add abstract methods
1 parent 4a8c84e commit 2d0869f

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

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

@@ -1316,6 +1319,18 @@ def drop_view(self, identifier: str) -> None:
13161319
except HTTPError as exc:
13171320
_handle_non_200_response(exc, {404: NoSuchViewError})
13181321

1322+
@retry(**_RETRY_ARGS)
1323+
def rename_view(self, from_identifier: Union[str, Identifier], to_identifier: Union[str, Identifier]) -> None:
1324+
payload = {
1325+
"source": self._split_identifier_for_json(from_identifier),
1326+
"destination": self._split_identifier_for_json(to_identifier),
1327+
}
1328+
response = self._session.post(self.url(Endpoints.rename_view), json=payload)
1329+
try:
1330+
response.raise_for_status()
1331+
except HTTPError as exc:
1332+
_handle_non_200_response(exc, {404: NoSuchViewError, 409: ViewAlreadyExistsError})
1333+
13191334
def close(self) -> None:
13201335
"""Close the catalog and release Session connection adapters.
13211336

tests/catalog/test_rest.py

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

2295+
<<<<<<< HEAD
22952296
def test_server_side_planning_disabled_by_default(self, rest_mock: Mocker) -> None:
22962297
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
22972298

@@ -2621,3 +2622,65 @@ def test_load_table_without_storage_credentials(
26212622
)
26222623
assert actual.metadata.model_dump() == expected.metadata.model_dump()
26232624
assert actual == expected
2625+
2626+
2627+
def test_rename_view_204(rest_mock: Mocker) -> None:
2628+
from_identifier = ("some_namespace", "old_view")
2629+
to_identifier = ("some_namespace", "new_view")
2630+
rest_mock.post(
2631+
f"{TEST_URI}v1/views/rename",
2632+
json={
2633+
"source": {"namespace": ["some_namespace"], "name": "old_view"},
2634+
"destination": {"namespace": ["some_namespace"], "name": "new_view"},
2635+
},
2636+
status_code=204,
2637+
request_headers=TEST_HEADERS,
2638+
)
2639+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
2640+
catalog.rename_view(from_identifier, to_identifier)
2641+
assert (
2642+
rest_mock.last_request.text
2643+
== """{"source": {"namespace": ["some_namespace"], "name": "old_view"}, "destination": {"namespace": ["some_namespace"], "name": "new_view"}}"""
2644+
)
2645+
2646+
2647+
def test_rename_view_404(rest_mock: Mocker) -> None:
2648+
from_identifier = ("some_namespace", "non_existent_view")
2649+
to_identifier = ("some_namespace", "new_view")
2650+
rest_mock.post(
2651+
f"{TEST_URI}v1/views/rename",
2652+
json={
2653+
"error": {
2654+
"message": "View does not exist: some_namespace.non_existent_view",
2655+
"type": "NoSuchViewException",
2656+
"code": 404,
2657+
}
2658+
},
2659+
status_code=404,
2660+
request_headers=TEST_HEADERS,
2661+
)
2662+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
2663+
with pytest.raises(NoSuchViewError) as exc_info:
2664+
catalog.rename_view(from_identifier, to_identifier)
2665+
assert "View does not exist: some_namespace.non_existent_view" in str(exc_info.value)
2666+
2667+
2668+
def test_rename_view_409(rest_mock: Mocker) -> None:
2669+
from_identifier = ("some_namespace", "old_view")
2670+
to_identifier = ("some_namespace", "existing_view")
2671+
rest_mock.post(
2672+
f"{TEST_URI}v1/views/rename",
2673+
json={
2674+
"error": {
2675+
"message": "View already exists: some_namespace.existing_view",
2676+
"type": "ViewAlreadyExistsException",
2677+
"code": 409,
2678+
}
2679+
},
2680+
status_code=409,
2681+
request_headers=TEST_HEADERS,
2682+
)
2683+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
2684+
with pytest.raises(ViewAlreadyExistsError) as exc_info:
2685+
catalog.rename_view(from_identifier, to_identifier)
2686+
assert "View already exists: some_namespace.existing_view" in str(exc_info.value)

0 commit comments

Comments
 (0)