Skip to content

Commit d9ee85d

Browse files
committed
Add drop_view to the rest catalog
1 parent c24448d commit d9ee85d

File tree

9 files changed

+85
-4
lines changed

9 files changed

+85
-4
lines changed

pyiceberg/catalog/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,17 @@ def update_namespace_properties(
567567
ValueError: If removals and updates have overlapping keys.
568568
"""
569569

570+
@abstractmethod
571+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
572+
"""Drop a view.
573+
574+
Args:
575+
identifier (str | Identifier): View identifier.
576+
577+
Raises:
578+
NoSuchViewError: If a view with the given name does not exist.
579+
"""
580+
570581
def identifier_to_tuple_without_catalog(self, identifier: Union[str, Identifier]) -> Identifier:
571582
"""Convert an identifier to a tuple and drop this catalog's name from the first element.
572583

pyiceberg/catalog/dynamodb.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,9 @@ def update_namespace_properties(
508508

509509
return properties_update_summary
510510

511+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
512+
raise NotImplementedError
513+
511514
def _get_iceberg_table_item(self, database_name: str, table_name: str) -> Dict[str, Any]:
512515
try:
513516
return self._get_dynamo_item(identifier=f"{database_name}.{table_name}", namespace=database_name)

pyiceberg/catalog/glue.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,3 +746,6 @@ def update_namespace_properties(
746746
self.glue.update_database(Name=database_name, DatabaseInput=_construct_database_input(database_name, updated_properties))
747747

748748
return properties_update_summary
749+
750+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
751+
raise NotImplementedError

pyiceberg/catalog/hive.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,3 +708,6 @@ def update_namespace_properties(
708708
expected_to_change = (removals or set()).difference(removed)
709709

710710
return PropertiesUpdateSummary(removed=list(removed or []), updated=list(updated or []), missing=list(expected_to_change))
711+
712+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
713+
raise NotImplementedError

pyiceberg/catalog/noop.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,6 @@ def update_namespace_properties(
113113
self, namespace: Union[str, Identifier], removals: Optional[Set[str]] = None, updates: Properties = EMPTY_DICT
114114
) -> PropertiesUpdateSummary:
115115
raise NotImplementedError
116+
117+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
118+
raise NotImplementedError

pyiceberg/catalog/rest.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,16 @@
4747
CommitStateUnknownException,
4848
ForbiddenError,
4949
NamespaceAlreadyExistsError,
50+
NoSuchIdentifierError,
5051
NoSuchNamespaceError,
5152
NoSuchTableError,
53+
NoSuchViewError,
5254
OAuthError,
5355
RESTError,
5456
ServerError,
5557
ServiceUnavailableError,
5658
TableAlreadyExistsError,
5759
UnauthorizedError,
58-
NoSuchIdentifierError,
5960
)
6061
from pyiceberg.partitioning import UNPARTITIONED_PARTITION_SPEC, PartitionSpec, assign_fresh_partition_spec_ids
6162
from pyiceberg.schema import Schema, assign_fresh_schema_ids
@@ -94,6 +95,7 @@ class Endpoints:
9495
table_exists: str = "namespaces/{namespace}/tables/{table}"
9596
get_token: str = "oauth/tokens"
9697
rename_table: str = "tables/rename"
98+
drop_view: str = "namespaces/{namespace}/views/{view}"
9799

98100

99101
AUTHORIZATION_HEADER = "Authorization"
@@ -352,11 +354,12 @@ def _identifier_to_validated_tuple(self, identifier: Union[str, Identifier]) ->
352354
raise NoSuchIdentifierError(f"Missing namespace or invalid identifier: {'.'.join(identifier_tuple)}")
353355
return identifier_tuple
354356

355-
def _split_identifier_for_path(self, identifier: Union[str, Identifier, TableIdentifier]) -> Properties:
357+
def _split_identifier_for_path(self, identifier: Union[str, Identifier, TableIdentifier], kind: str = "table") -> Properties:
356358
if isinstance(identifier, TableIdentifier):
357359
return {"namespace": NAMESPACE_SEPARATOR.join(identifier.namespace.root[1:]), "table": identifier.name}
358360
identifier_tuple = self._identifier_to_validated_tuple(identifier)
359-
return {"namespace": NAMESPACE_SEPARATOR.join(identifier_tuple[:-1]), "table": identifier_tuple[-1]}
361+
362+
return {"namespace": NAMESPACE_SEPARATOR.join(identifier_tuple[:-1]), kind: identifier_tuple[-1]}
360363

361364
def _split_identifier_for_json(self, identifier: Union[str, Identifier]) -> Dict[str, Union[Identifier, str]]:
362365
identifier_tuple = self._identifier_to_validated_tuple(identifier)
@@ -792,3 +795,14 @@ def table_exists(self, identifier: Union[str, Identifier]) -> bool:
792795
self.url(Endpoints.load_table, prefixed=True, **self._split_identifier_for_path(identifier_tuple))
793796
)
794797
return response.status_code in (200, 204)
798+
799+
@retry(**_RETRY_ARGS)
800+
def drop_view(self, identifier: Union[str]) -> None:
801+
identifier_tuple = self.identifier_to_tuple_without_catalog(identifier)
802+
response = self._session.delete(
803+
self.url(Endpoints.drop_view, prefixed=True, **self._split_identifier_for_path(identifier_tuple, kind="view")),
804+
)
805+
try:
806+
response.raise_for_status()
807+
except HTTPError as exc:
808+
self._handle_non_200_response(exc, {404: NoSuchViewError})

pyiceberg/catalog/sql.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,3 +681,6 @@ def update_namespace_properties(
681681
session.execute(insert_stmt)
682682
session.commit()
683683
return properties_update_summary
684+
685+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
686+
raise NotImplementedError

pyiceberg/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class NoSuchIcebergTableError(NoSuchTableError):
4040
"""Raises when the table found in the REST catalog is not an iceberg table."""
4141

4242

43+
class NoSuchViewError(Exception):
44+
"""Raises when the view can't be found in the REST catalog."""
4345

4446

4547
class NoSuchIdentifierError(Exception):

tests/catalog/test_rest.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@
2828
from pyiceberg.exceptions import (
2929
AuthorizationExpiredError,
3030
NamespaceAlreadyExistsError,
31+
NoSuchIdentifierError,
3132
NoSuchNamespaceError,
3233
NoSuchTableError,
34+
NoSuchViewError,
3335
OAuthError,
3436
TableAlreadyExistsError,
35-
NoSuchIdentifierError,
3637
)
3738
from pyiceberg.io import load_file_io
3839
from pyiceberg.partitioning import PartitionField, PartitionSpec
@@ -1207,3 +1208,41 @@ def test_catalog_from_parameters_empty_env(rest_mock: Mocker) -> None:
12071208

12081209
catalog = cast(RestCatalog, load_catalog("production", uri="https://other-service.io/api"))
12091210
assert catalog.uri == "https://other-service.io/api"
1211+
1212+
1213+
def test_drop_view_invalid_namespace(rest_mock: Mocker) -> None:
1214+
view = "view"
1215+
with pytest.raises(NoSuchIdentifierError) as e:
1216+
# Missing namespace
1217+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(view)
1218+
1219+
assert f"Missing namespace or invalid identifier: {view}" in str(e.value)
1220+
1221+
1222+
def test_drop_view_404(rest_mock: Mocker) -> None:
1223+
rest_mock.delete(
1224+
f"{TEST_URI}v1/namespaces/some_namespace/views/does_not_exists",
1225+
json={
1226+
"error": {
1227+
"message": "The given view does not exist",
1228+
"type": "NoSuchViewException",
1229+
"code": 404,
1230+
}
1231+
},
1232+
status_code=404,
1233+
request_headers=TEST_HEADERS,
1234+
)
1235+
1236+
with pytest.raises(NoSuchViewError) as e:
1237+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(("some_namespace", "does_not_exists"))
1238+
assert "The given view does not exist" in str(e.value)
1239+
1240+
1241+
def test_drop_view_204(rest_mock: Mocker) -> None:
1242+
rest_mock.delete(
1243+
f"{TEST_URI}v1/namespaces/some_namespace/views/some_view",
1244+
json={},
1245+
status_code=204,
1246+
request_headers=TEST_HEADERS,
1247+
)
1248+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(("some_namespace", "some_view"))

0 commit comments

Comments
 (0)