Skip to content

Commit 11a5fd4

Browse files
committed
Add drop_view to the rest catalog
1 parent d9edcb5 commit 11a5fd4

File tree

11 files changed

+121
-4
lines changed

11 files changed

+121
-4
lines changed

pyiceberg/catalog/__init__.py

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

616+
@abstractmethod
617+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
618+
"""Drop a view.
619+
620+
Args:
621+
identifier (str | Identifier): View identifier.
622+
623+
Raises:
624+
NoSuchViewError: If a view with the given name does not exist.
625+
"""
626+
616627
@deprecated(
617628
deprecated_in="0.8.0",
618629
removed_in="0.9.0",

pyiceberg/catalog/dynamodb.py

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

529529
return properties_update_summary
530530

531+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
532+
raise NotImplementedError
533+
531534
def _get_iceberg_table_item(self, database_name: str, table_name: str) -> Dict[str, Any]:
532535
try:
533536
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
@@ -769,3 +769,6 @@ def update_namespace_properties(
769769
self.glue.update_database(Name=database_name, DatabaseInput=_construct_database_input(database_name, updated_properties))
770770

771771
return properties_update_summary
772+
773+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
774+
raise NotImplementedError

pyiceberg/catalog/hive.py

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

706706
return PropertiesUpdateSummary(removed=list(removed or []), updated=list(updated or []), missing=list(expected_to_change))
707+
708+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
709+
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
@@ -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
@@ -97,6 +98,7 @@ class Endpoints:
9798
table_exists: str = "namespaces/{namespace}/tables/{table}"
9899
get_token: str = "oauth/tokens"
99100
rename_table: str = "tables/rename"
101+
drop_view: str = "namespaces/{namespace}/views/{view}"
100102

101103

102104
AUTHORIZATION_HEADER = "Authorization"
@@ -383,14 +385,15 @@ def _identifier_to_validated_tuple(self, identifier: Union[str, Identifier]) ->
383385
raise NoSuchIdentifierError(f"Missing namespace or invalid identifier: {'.'.join(identifier_tuple)}")
384386
return identifier_tuple
385387

386-
def _split_identifier_for_path(self, identifier: Union[str, Identifier, TableIdentifier]) -> Properties:
388+
def _split_identifier_for_path(self, identifier: Union[str, Identifier, TableIdentifier], kind: str = "table") -> Properties:
387389
if isinstance(identifier, TableIdentifier):
388390
if identifier.namespace.root[0] == self.name:
389391
return {"namespace": NAMESPACE_SEPARATOR.join(identifier.namespace.root[1:]), "table": identifier.name}
390392
else:
391393
return {"namespace": NAMESPACE_SEPARATOR.join(identifier.namespace.root), "table": identifier.name}
392394
identifier_tuple = self._identifier_to_validated_tuple(identifier)
393-
return {"namespace": NAMESPACE_SEPARATOR.join(identifier_tuple[:-1]), "table": identifier_tuple[-1]}
395+
396+
return {"namespace": NAMESPACE_SEPARATOR.join(identifier_tuple[:-1]), kind: identifier_tuple[-1]}
394397

395398
def _split_identifier_for_json(self, identifier: Union[str, Identifier]) -> Dict[str, Union[Identifier, str]]:
396399
identifier_tuple = self._identifier_to_validated_tuple(identifier)
@@ -836,3 +839,14 @@ def table_exists(self, identifier: Union[str, Identifier]) -> bool:
836839
self.url(Endpoints.load_table, prefixed=True, **self._split_identifier_for_path(identifier_tuple))
837840
)
838841
return response.status_code in (200, 204)
842+
843+
@retry(**_RETRY_ARGS)
844+
def drop_view(self, identifier: Union[str]) -> None:
845+
identifier_tuple = self.identifier_to_tuple_without_catalog(identifier)
846+
response = self._session.delete(
847+
self.url(Endpoints.drop_view, prefixed=True, **self._split_identifier_for_path(identifier_tuple, kind="view")),
848+
)
849+
try:
850+
response.raise_for_status()
851+
except HTTPError as exc:
852+
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
@@ -696,3 +696,6 @@ def update_namespace_properties(
696696
session.execute(insert_stmt)
697697
session.commit()
698698
return properties_update_summary
699+
700+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
701+
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):

pyiceberg/table/procedures.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pyiceberg.table import Table
2+
from typing import Dict, Any, Callable
3+
4+
5+
class ExpireSnashots:
6+
# Expire snapshots older than 3 days (timestampMillis a long timestamp)
7+
DEFAULT_OPTS = {
8+
"expire_older_than": 3 * 24 * 60 * 60 * 1000,
9+
"retain_last": True,
10+
}
11+
12+
expired_snapshots = []
13+
14+
def __init__(self, table: Table, opts: Dict[str, Any], delete_with: Callable[[str], None] = None):
15+
self.table = table
16+
self.opts = {**self.DEFAULT_OPTS, **opts}
17+
self.delete_with = delete_with
18+
19+
def collect(self):
20+
if len(self.expired_snapshots) > 0:
21+
self.expired_snapshots = []
22+
23+
_collect_expired_snapshots()
24+
25+
return self
26+
27+
def commit(self):
28+
# commit the changes
29+
pass
30+
31+
def _collect_expired_snapshots(self):
32+
# collect expired snapshots
33+
pass

tests/catalog/test_base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ def update_namespace_properties(
256256
removed=list(removed or []), updated=list(updates.keys() if updates else []), missing=list(expected_to_change)
257257
)
258258

259+
def drop_view(self, identifier: Union[str, Identifier]) -> None:
260+
raise NotImplementedError
261+
259262

260263
@pytest.fixture
261264
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,11 +29,12 @@
2929
AuthorizationExpiredError,
3030
NamespaceAlreadyExistsError,
3131
NamespaceNotEmptyError,
32+
NoSuchIdentifierError,
3233
NoSuchNamespaceError,
3334
NoSuchTableError,
35+
NoSuchViewError,
3436
OAuthError,
3537
TableAlreadyExistsError,
36-
NoSuchIdentifierError,
3738
)
3839
from pyiceberg.io import load_file_io
3940
from pyiceberg.partitioning import PartitionField, PartitionSpec
@@ -1249,3 +1250,41 @@ def test_table_identifier_in_commit_table_request(rest_mock: Mocker, example_tab
12491250
rest_mock.last_request.text
12501251
== """{"identifier":{"namespace":["namespace"],"name":"table_name"},"requirements":[],"updates":[]}"""
12511252
)
1253+
1254+
1255+
def test_drop_view_invalid_namespace(rest_mock: Mocker) -> None:
1256+
view = "view"
1257+
with pytest.raises(NoSuchIdentifierError) as e:
1258+
# Missing namespace
1259+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(view)
1260+
1261+
assert f"Missing namespace or invalid identifier: {view}" in str(e.value)
1262+
1263+
1264+
def test_drop_view_404(rest_mock: Mocker) -> None:
1265+
rest_mock.delete(
1266+
f"{TEST_URI}v1/namespaces/some_namespace/views/does_not_exists",
1267+
json={
1268+
"error": {
1269+
"message": "The given view does not exist",
1270+
"type": "NoSuchViewException",
1271+
"code": 404,
1272+
}
1273+
},
1274+
status_code=404,
1275+
request_headers=TEST_HEADERS,
1276+
)
1277+
1278+
with pytest.raises(NoSuchViewError) as e:
1279+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(("some_namespace", "does_not_exists"))
1280+
assert "The given view does not exist" in str(e.value)
1281+
1282+
1283+
def test_drop_view_204(rest_mock: Mocker) -> None:
1284+
rest_mock.delete(
1285+
f"{TEST_URI}v1/namespaces/some_namespace/views/some_view",
1286+
json={},
1287+
status_code=204,
1288+
request_headers=TEST_HEADERS,
1289+
)
1290+
RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).drop_view(("some_namespace", "some_view"))

0 commit comments

Comments
 (0)