Skip to content

Commit 2756625

Browse files
committed
Add drop_view to the rest catalog
1 parent 9881f11 commit 2756625

File tree

10 files changed

+88
-4
lines changed

10 files changed

+88
-4
lines changed

pyiceberg/catalog/__init__.py

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

632+
@abstractmethod
633+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
634+
"""Drop a view.
635+
636+
Args:
637+
identifier (str | Identifier): View identifier.
638+
639+
Raises:
640+
NoSuchViewError: If a view with the given name does not exist.
641+
"""
642+
632643
@deprecated(
633644
deprecated_in="0.8.0",
634645
removed_in="0.9.0",

pyiceberg/catalog/dynamodb.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,9 @@ def update_namespace_properties(
531531
def list_views(self, namespace: Union[str, Identifier]) -> List[Identifier]:
532532
raise NotImplementedError
533533

534+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
535+
raise NotImplementedError
536+
534537
def _get_iceberg_table_item(self, database_name: str, table_name: str) -> Dict[str, Any]:
535538
try:
536539
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
@@ -772,3 +772,6 @@ def update_namespace_properties(
772772

773773
def list_views(self, namespace: Union[str, Identifier]) -> List[Identifier]:
774774
raise NotImplementedError
775+
776+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
777+
raise NotImplementedError

pyiceberg/catalog/hive.py

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

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

pyiceberg/catalog/noop.py

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

117117
def list_views(self, namespace: Union[str, Identifier]) -> List[Identifier]:
118118
raise NotImplementedError
119+
120+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
121+
raise NotImplementedError

pyiceberg/catalog/rest.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,16 @@
4848
ForbiddenError,
4949
NamespaceAlreadyExistsError,
5050
NamespaceNotEmptyError,
51+
NoSuchIdentifierError,
5152
NoSuchNamespaceError,
5253
NoSuchTableError,
54+
NoSuchViewError,
5355
OAuthError,
5456
RESTError,
5557
ServerError,
5658
ServiceUnavailableError,
5759
TableAlreadyExistsError,
5860
UnauthorizedError,
59-
NoSuchIdentifierError,
6061
)
6162
from pyiceberg.partitioning import UNPARTITIONED_PARTITION_SPEC, PartitionSpec, assign_fresh_partition_spec_ids
6263
from pyiceberg.schema import Schema, assign_fresh_schema_ids
@@ -98,6 +99,7 @@ class Endpoints:
9899
get_token: str = "oauth/tokens"
99100
rename_table: str = "tables/rename"
100101
list_views: str = "namespaces/{namespace}/views"
102+
drop_view: str = "namespaces/{namespace}/views/{view}"
101103

102104

103105
AUTHORIZATION_HEADER = "Authorization"
@@ -393,14 +395,15 @@ def _identifier_to_validated_tuple(self, identifier: Union[str, Identifier]) ->
393395
raise NoSuchIdentifierError(f"Missing namespace or invalid identifier: {'.'.join(identifier_tuple)}")
394396
return identifier_tuple
395397

396-
def _split_identifier_for_path(self, identifier: Union[str, Identifier, TableIdentifier]) -> Properties:
398+
def _split_identifier_for_path(self, identifier: Union[str, Identifier, TableIdentifier], kind: str = "table") -> Properties:
397399
if isinstance(identifier, TableIdentifier):
398400
if identifier.namespace.root[0] == self.name:
399401
return {"namespace": NAMESPACE_SEPARATOR.join(identifier.namespace.root[1:]), "table": identifier.name}
400402
else:
401403
return {"namespace": NAMESPACE_SEPARATOR.join(identifier.namespace.root), "table": identifier.name}
402404
identifier_tuple = self._identifier_to_validated_tuple(identifier)
403-
return {"namespace": NAMESPACE_SEPARATOR.join(identifier_tuple[:-1]), "table": identifier_tuple[-1]}
405+
406+
return {"namespace": NAMESPACE_SEPARATOR.join(identifier_tuple[:-1]), kind: identifier_tuple[-1]}
404407

405408
def _split_identifier_for_json(self, identifier: Union[str, Identifier]) -> Dict[str, Union[Identifier, str]]:
406409
identifier_tuple = self._identifier_to_validated_tuple(identifier)
@@ -868,3 +871,14 @@ def table_exists(self, identifier: Union[str, Identifier]) -> bool:
868871
self._handle_non_200_response(exc, {})
869872

870873
return False
874+
875+
@retry(**_RETRY_ARGS)
876+
def drop_view(self, identifier: Union[str]) -> None:
877+
identifier_tuple = self.identifier_to_tuple_without_catalog(identifier)
878+
response = self._session.delete(
879+
self.url(Endpoints.drop_view, prefixed=True, **self._split_identifier_for_path(identifier_tuple, kind="view")),
880+
)
881+
try:
882+
response.raise_for_status()
883+
except HTTPError as exc:
884+
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
@@ -699,3 +699,6 @@ def update_namespace_properties(
699699

700700
def list_views(self, namespace: Union[str, Identifier]) -> List[Identifier]:
701701
raise NotImplementedError
702+
703+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
704+
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_base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ def update_namespace_properties(
259259
def list_views(self, namespace: Optional[Union[str, Identifier]] = None) -> List[Identifier]:
260260
raise NotImplementedError
261261

262+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
263+
raise NotImplementedError
264+
262265

263266
@pytest.fixture
264267
def catalog(tmp_path: PosixPath) -> InMemoryCatalog:

tests/catalog/test_rest.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@
2929
AuthorizationExpiredError,
3030
NamespaceAlreadyExistsError,
3131
NamespaceNotEmptyError,
32+
NoSuchIdentifierError,
3233
NoSuchNamespaceError,
3334
NoSuchTableError,
35+
NoSuchViewError,
3436
OAuthError,
3537
ServerError,
3638
TableAlreadyExistsError,
37-
NoSuchIdentifierError,
3839
)
3940
from pyiceberg.io import load_file_io
4041
from pyiceberg.partitioning import PartitionField, PartitionSpec
@@ -1308,3 +1309,41 @@ def test_table_identifier_in_commit_table_request(rest_mock: Mocker, example_tab
13081309
rest_mock.last_request.text
13091310
== """{"identifier":{"namespace":["namespace"],"name":"table_name"},"requirements":[],"updates":[]}"""
13101311
)
1312+
1313+
1314+
def test_drop_view_invalid_namespace(rest_mock: Mocker) -> None:
1315+
view = "view"
1316+
with pytest.raises(NoSuchIdentifierError) as e:
1317+
# Missing namespace
1318+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(view)
1319+
1320+
assert f"Missing namespace or invalid identifier: {view}" in str(e.value)
1321+
1322+
1323+
def test_drop_view_404(rest_mock: Mocker) -> None:
1324+
rest_mock.delete(
1325+
f"{TEST_URI}v1/namespaces/some_namespace/views/does_not_exists",
1326+
json={
1327+
"error": {
1328+
"message": "The given view does not exist",
1329+
"type": "NoSuchViewException",
1330+
"code": 404,
1331+
}
1332+
},
1333+
status_code=404,
1334+
request_headers=TEST_HEADERS,
1335+
)
1336+
1337+
with pytest.raises(NoSuchViewError) as e:
1338+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(("some_namespace", "does_not_exists"))
1339+
assert "The given view does not exist" in str(e.value)
1340+
1341+
1342+
def test_drop_view_204(rest_mock: Mocker) -> None:
1343+
rest_mock.delete(
1344+
f"{TEST_URI}v1/namespaces/some_namespace/views/some_view",
1345+
json={},
1346+
status_code=204,
1347+
request_headers=TEST_HEADERS,
1348+
)
1349+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(("some_namespace", "some_view"))

0 commit comments

Comments
 (0)