Skip to content

Commit c91afad

Browse files
committed
just need to add abstract methods
1 parent ca70442 commit c91afad

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

pyiceberg/catalog/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,21 @@ def drop_view(self, identifier: Union[str, Identifier]) -> None:
657657
NoSuchViewError: If a view with the given name does not exist.
658658
"""
659659

660+
@abstractmethod
661+
def rename_view(self, from_identifier: Union[str, Identifier], to_identifier: Union[str, Identifier]) -> None:
662+
"""Rename a fully classified view name.
663+
664+
Args:
665+
from_identifier (str | Identifier): Existing view identifier.
666+
to_identifier (str | Identifier): New view identifier.
667+
668+
Returns:
669+
Table: the updated table instance with its metadata.
670+
671+
Raises:
672+
NoSuchViewError: If a table with the name does not exist.
673+
"""
674+
660675
@staticmethod
661676
def identifier_to_tuple(identifier: Union[str, Identifier]) -> Identifier:
662677
"""Parse an identifier to a tuple.

pyiceberg/catalog/rest/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
NoSuchViewError,
5454
TableAlreadyExistsError,
5555
UnauthorizedError,
56+
ViewAlreadyExistsError,
5657
)
5758
from pyiceberg.io import AWS_ACCESS_KEY_ID, AWS_REGION, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
5859
from pyiceberg.partitioning import UNPARTITIONED_PARTITION_SPEC, PartitionSpec, assign_fresh_partition_spec_ids
@@ -102,6 +103,7 @@ class Endpoints:
102103
list_views: str = "namespaces/{namespace}/views"
103104
drop_view: str = "namespaces/{namespace}/views/{view}"
104105
view_exists: str = "namespaces/{namespace}/views/{view}"
106+
rename_view: str = "views/rename"
105107

106108

107109
class IdentifierKind(Enum):
@@ -857,3 +859,15 @@ def drop_view(self, identifier: Union[str]) -> None:
857859
response.raise_for_status()
858860
except HTTPError as exc:
859861
_handle_non_200_response(exc, {404: NoSuchViewError})
862+
863+
@retry(**_RETRY_ARGS)
864+
def rename_view(self, from_identifier: Union[str, Identifier], to_identifier: Union[str, Identifier]) -> None:
865+
payload = {
866+
"source": self._split_identifier_for_json(from_identifier),
867+
"destination": self._split_identifier_for_json(to_identifier),
868+
}
869+
response = self._session.post(self.url(Endpoints.rename_view), json=payload)
870+
try:
871+
response.raise_for_status()
872+
except HTTPError as exc:
873+
_handle_non_200_response(exc, {404: NoSuchViewError, 409: ViewAlreadyExistsError})

pyiceberg/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class NoSuchViewError(Exception):
4444
"""Raises when the view can't be found in the REST catalog."""
4545

4646

47+
class ViewAlreadyExistsError(Exception):
48+
"""Raises when the view being created already exists in the REST catalog."""
49+
50+
4751
class NoSuchIdentifierError(Exception):
4852
"""Raises when the identifier can't be found in the REST catalog."""
4953

tests/catalog/test_rest.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
OAuthError,
3737
ServerError,
3838
TableAlreadyExistsError,
39+
ViewAlreadyExistsError,
3940
)
4041
from pyiceberg.io import load_file_io
4142
from pyiceberg.partitioning import PartitionField, PartitionSpec
@@ -1621,3 +1622,65 @@ def test_drop_view_204(rest_mock: Mocker) -> None:
16211622
request_headers=TEST_HEADERS,
16221623
)
16231624
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(("some_namespace", "some_view"))
1625+
1626+
1627+
def test_rename_view_204(rest_mock: Mocker) -> None:
1628+
from_identifier = ("some_namespace", "old_view")
1629+
to_identifier = ("some_namespace", "new_view")
1630+
rest_mock.post(
1631+
f"{TEST_URI}v1/views/rename",
1632+
json={
1633+
"source": {"namespace": ["some_namespace"], "name": "old_view"},
1634+
"destination": {"namespace": ["some_namespace"], "name": "new_view"},
1635+
},
1636+
status_code=204,
1637+
request_headers=TEST_HEADERS,
1638+
)
1639+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
1640+
catalog.rename_view(from_identifier, to_identifier)
1641+
assert (
1642+
rest_mock.last_request.text
1643+
== """{"source": {"namespace": ["some_namespace"], "name": "old_view"}, "destination": {"namespace": ["some_namespace"], "name": "new_view"}}"""
1644+
)
1645+
1646+
1647+
def test_rename_view_404(rest_mock: Mocker) -> None:
1648+
from_identifier = ("some_namespace", "non_existent_view")
1649+
to_identifier = ("some_namespace", "new_view")
1650+
rest_mock.post(
1651+
f"{TEST_URI}v1/views/rename",
1652+
json={
1653+
"error": {
1654+
"message": "View does not exist: some_namespace.non_existent_view",
1655+
"type": "NoSuchViewException",
1656+
"code": 404,
1657+
}
1658+
},
1659+
status_code=404,
1660+
request_headers=TEST_HEADERS,
1661+
)
1662+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
1663+
with pytest.raises(NoSuchViewError) as exc_info:
1664+
catalog.rename_view(from_identifier, to_identifier)
1665+
assert "View does not exist: some_namespace.non_existent_view" in str(exc_info.value)
1666+
1667+
1668+
def test_rename_view_409(rest_mock: Mocker) -> None:
1669+
from_identifier = ("some_namespace", "old_view")
1670+
to_identifier = ("some_namespace", "existing_view")
1671+
rest_mock.post(
1672+
f"{TEST_URI}v1/views/rename",
1673+
json={
1674+
"error": {
1675+
"message": "View already exists: some_namespace.existing_view",
1676+
"type": "ViewAlreadyExistsException",
1677+
"code": 409,
1678+
}
1679+
},
1680+
status_code=409,
1681+
request_headers=TEST_HEADERS,
1682+
)
1683+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
1684+
with pytest.raises(ViewAlreadyExistsError) as exc_info:
1685+
catalog.rename_view(from_identifier, to_identifier)
1686+
assert "View already exists: some_namespace.existing_view" in str(exc_info.value)

0 commit comments

Comments
 (0)