Skip to content

Commit 33bfbc8

Browse files
authored
Add namespace_exists method (#2972)
<!-- Thanks for opening a pull request! --> <!-- In the case this PR will resolve an issue, please replace ${GITHUB_ISSUE_ID} below with the actual Github issue id. --> Closes #2969 # Rationale for this change We should make `namespace_exists` available on every catalog. ## Are these changes tested? Includes unit tests. Many catalogs have a private method doing this already. Look at all of the type exceptions being deleted! ## Are there any user-facing changes? - Adds namespace_exists method. <!-- In the case of user-facing changes, please add the changelog label. -->
1 parent 1f947a7 commit 33bfbc8

File tree

5 files changed

+54
-22
lines changed

5 files changed

+54
-22
lines changed

pyiceberg/catalog/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,17 @@ def view_exists(self, identifier: str | Identifier) -> bool:
470470
bool: True if the view exists, False otherwise.
471471
"""
472472

473+
@abstractmethod
474+
def namespace_exists(self, namespace: str | Identifier) -> bool:
475+
"""Check if a namespace exists.
476+
477+
Args:
478+
namespace (str | Identifier): Namespace identifier.
479+
480+
Returns:
481+
bool: True if the namespace exists, False otherwise.
482+
"""
483+
473484
@abstractmethod
474485
def register_table(self, identifier: str | Identifier, metadata_location: str) -> Table:
475486
"""Register a new table using existing metadata.
@@ -845,6 +856,21 @@ def table_exists(self, identifier: str | Identifier) -> bool:
845856
except NoSuchTableError:
846857
return False
847858

859+
def namespace_exists(self, namespace: str | Identifier) -> bool:
860+
"""Check if a namespace exists.
861+
862+
Args:
863+
namespace (str | Identifier): Namespace identifier.
864+
865+
Returns:
866+
bool: True if the namespace exists, False otherwise.
867+
"""
868+
try:
869+
self.load_namespace_properties(namespace)
870+
return True
871+
except NoSuchNamespaceError:
872+
return False
873+
848874
def purge_table(self, identifier: str | Identifier) -> None:
849875
table = self.load_table(identifier)
850876
self.drop_table(identifier)

pyiceberg/catalog/noop.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,8 @@ def list_views(self, namespace: str | Identifier) -> list[Identifier]:
122122
def view_exists(self, identifier: str | Identifier) -> bool:
123123
raise NotImplementedError
124124

125+
def namespace_exists(self, namespace: str | Identifier) -> bool:
126+
raise NotImplementedError
127+
125128
def drop_view(self, identifier: str | Identifier) -> None:
126129
raise NotImplementedError

pyiceberg/catalog/sql.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def create_table(
204204

205205
namespace_identifier = Catalog.namespace_from(identifier)
206206
table_name = Catalog.table_name_from(identifier)
207-
if not self._namespace_exists(namespace_identifier):
207+
if not self.namespace_exists(namespace_identifier):
208208
raise NoSuchNamespaceError(f"Namespace does not exist: {namespace_identifier}")
209209

210210
namespace = Catalog.namespace_to_string(namespace_identifier)
@@ -251,7 +251,7 @@ def register_table(self, identifier: str | Identifier, metadata_location: str) -
251251
namespace_tuple = Catalog.namespace_from(identifier)
252252
namespace = Catalog.namespace_to_string(namespace_tuple)
253253
table_name = Catalog.table_name_from(identifier)
254-
if not self._namespace_exists(namespace):
254+
if not self.namespace_exists(namespace):
255255
raise NoSuchNamespaceError(f"Namespace does not exist: {namespace}")
256256

257257
with Session(self.engine) as session:
@@ -361,7 +361,7 @@ def rename_table(self, from_identifier: str | Identifier, to_identifier: str | I
361361
to_namespace_tuple = Catalog.namespace_from(to_identifier)
362362
to_namespace = Catalog.namespace_to_string(to_namespace_tuple)
363363
to_table_name = Catalog.table_name_from(to_identifier)
364-
if not self._namespace_exists(to_namespace):
364+
if not self.namespace_exists(to_namespace):
365365
raise NoSuchNamespaceError(f"Namespace does not exist: {to_namespace}")
366366
with Session(self.engine) as session:
367367
try:
@@ -495,7 +495,7 @@ def commit_table(
495495
metadata=updated_staged_table.metadata, metadata_location=updated_staged_table.metadata_location
496496
)
497497

498-
def _namespace_exists(self, identifier: str | Identifier) -> bool:
498+
def namespace_exists(self, identifier: str | Identifier) -> bool:
499499
namespace_tuple = Catalog.identifier_to_tuple(identifier)
500500
namespace = Catalog.namespace_to_string(namespace_tuple, NoSuchNamespaceError)
501501
namespace_starts_with = namespace.replace("!", "!!").replace("_", "!_").replace("%", "!%") + ".%"
@@ -537,7 +537,7 @@ def create_namespace(self, namespace: str | Identifier, properties: Properties =
537537
Raises:
538538
NamespaceAlreadyExistsError: If a namespace with the given name already exists.
539539
"""
540-
if self._namespace_exists(namespace):
540+
if self.namespace_exists(namespace):
541541
raise NamespaceAlreadyExistsError(f"Namespace {namespace} already exists")
542542

543543
if not properties:
@@ -565,7 +565,7 @@ def drop_namespace(self, namespace: str | Identifier) -> None:
565565
NoSuchNamespaceError: If a namespace with the given name does not exist.
566566
NamespaceNotEmptyError: If the namespace is not empty.
567567
"""
568-
if not self._namespace_exists(namespace):
568+
if not self.namespace_exists(namespace):
569569
raise NoSuchNamespaceError(f"Namespace does not exist: {namespace}")
570570

571571
namespace_str = Catalog.namespace_to_string(namespace)
@@ -593,7 +593,7 @@ def list_tables(self, namespace: str | Identifier) -> list[Identifier]:
593593
Raises:
594594
NoSuchNamespaceError: If a namespace with the given name does not exist.
595595
"""
596-
if namespace and not self._namespace_exists(namespace):
596+
if namespace and not self.namespace_exists(namespace):
597597
raise NoSuchNamespaceError(f"Namespace does not exist: {namespace}")
598598

599599
namespace = Catalog.namespace_to_string(namespace)
@@ -614,7 +614,7 @@ def list_namespaces(self, namespace: str | Identifier = ()) -> list[Identifier]:
614614
Raises:
615615
NoSuchNamespaceError: If a namespace with the given name does not exist.
616616
"""
617-
if namespace and not self._namespace_exists(namespace):
617+
if namespace and not self.namespace_exists(namespace):
618618
raise NoSuchNamespaceError(f"Namespace does not exist: {namespace}")
619619

620620
table_stmt = select(IcebergTables.table_namespace).where(IcebergTables.catalog_name == self.name)
@@ -656,7 +656,7 @@ def load_namespace_properties(self, namespace: str | Identifier) -> Properties:
656656
NoSuchNamespaceError: If a namespace with the given name does not exist.
657657
"""
658658
namespace_str = Catalog.namespace_to_string(namespace)
659-
if not self._namespace_exists(namespace):
659+
if not self.namespace_exists(namespace):
660660
raise NoSuchNamespaceError(f"Namespace {namespace_str} does not exists")
661661

662662
stmt = select(IcebergNamespaceProperties).where(
@@ -681,7 +681,7 @@ def update_namespace_properties(
681681
ValueError: If removals and updates have overlapping keys.
682682
"""
683683
namespace_str = Catalog.namespace_to_string(namespace)
684-
if not self._namespace_exists(namespace):
684+
if not self.namespace_exists(namespace):
685685
raise NoSuchNamespaceError(f"Namespace {namespace_str} does not exists")
686686

687687
current_properties = self.load_namespace_properties(namespace=namespace)

tests/catalog/test_catalog_behaviors.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ def test_add_column_with_statement(catalog: Catalog, table_schema_simple: Schema
946946

947947
def test_create_namespace(catalog: Catalog, test_namespace: Identifier, test_table_properties: dict[str, str]) -> None:
948948
catalog.create_namespace(test_namespace, test_table_properties)
949-
assert catalog._namespace_exists(test_namespace) # type: ignore[attr-defined]
949+
assert catalog.namespace_exists(test_namespace)
950950
assert (Catalog.identifier_to_tuple(test_namespace)[:1]) in catalog.list_namespaces()
951951
assert test_table_properties == catalog.load_namespace_properties(test_namespace)
952952

@@ -1015,16 +1015,16 @@ def test_get_namespace_metadata_raises_error_when_namespace_does_not_exist(catal
10151015
def test_namespace_exists(catalog: Catalog) -> None:
10161016
for ns in [("db1",), ("db1", "ns1"), ("db2", "ns1"), ("db3", "ns1", "ns2")]:
10171017
catalog.create_namespace(ns)
1018-
assert catalog._namespace_exists(ns) # type: ignore[attr-defined]
1018+
assert catalog.namespace_exists(ns)
10191019

10201020
# `db2` exists because `db2.ns1` exists
1021-
assert catalog._namespace_exists("db2") # type: ignore[attr-defined]
1021+
assert catalog.namespace_exists("db2")
10221022
# `db3.ns1` exists because `db3.ns1.ns2` exists
1023-
assert catalog._namespace_exists("db3.ns1") # type: ignore[attr-defined]
1023+
assert catalog.namespace_exists("db3.ns1")
10241024
# make sure '_' is escaped in the query
1025-
assert not catalog._namespace_exists("db_") # type: ignore[attr-defined]
1025+
assert not catalog.namespace_exists("db_")
10261026
# make sure '%' is escaped in the query
1027-
assert not catalog._namespace_exists("db%") # type: ignore[attr-defined]
1027+
assert not catalog.namespace_exists("db%")
10281028

10291029

10301030
# Namespace properties
@@ -1064,7 +1064,7 @@ def test_load_empty_namespace_properties(catalog: Catalog, test_namespace: Ident
10641064
def test_list_namespaces(catalog: Catalog) -> None:
10651065
namespace_list = ["db", "db.ns1", "db.ns1.ns2", "db.ns2", "db2", "db2.ns1", "db%"]
10661066
for namespace in namespace_list:
1067-
if not catalog._namespace_exists(namespace): # type: ignore[attr-defined]
1067+
if not catalog.namespace_exists(namespace):
10681068
catalog.create_namespace(namespace)
10691069

10701070
ns_list = catalog.list_namespaces()
@@ -1084,7 +1084,7 @@ def test_list_namespaces(catalog: Catalog) -> None:
10841084
def test_list_namespaces_fuzzy_match(catalog: Catalog) -> None:
10851085
namespace_list = ["db.ns1", "db.ns1.ns2", "db.ns2", "db.ns1X.ns3", "db_.ns1.ns2", "db2.ns1.ns2"]
10861086
for namespace in namespace_list:
1087-
if not catalog._namespace_exists(namespace): # type: ignore[attr-defined]
1087+
if not catalog.namespace_exists(namespace):
10881088
catalog.create_namespace(namespace)
10891089

10901090
assert catalog.list_namespaces("db.ns1") == [("db", "ns1", "ns2")]
@@ -1140,7 +1140,7 @@ def test_update_namespace_metadata(catalog: Catalog, test_namespace: Identifier,
11401140
catalog.create_namespace(test_namespace, test_table_properties)
11411141
new_metadata = {"key3": "value3", "key4": "value4"}
11421142
summary = catalog.update_namespace_properties(test_namespace, updates=new_metadata)
1143-
assert catalog._namespace_exists(test_namespace) # type: ignore[attr-defined]
1143+
assert catalog.namespace_exists(test_namespace)
11441144
assert new_metadata.items() <= catalog.load_namespace_properties(test_namespace).items()
11451145
assert summary.removed == []
11461146
assert sorted(summary.updated) == ["key3", "key4"]
@@ -1154,7 +1154,7 @@ def test_update_namespace_metadata_removals(
11541154
new_metadata = {"key3": "value3", "key4": "value4"}
11551155
remove_metadata = {"key1"}
11561156
summary = catalog.update_namespace_properties(test_namespace, remove_metadata, new_metadata)
1157-
assert catalog._namespace_exists(test_namespace) # type: ignore[attr-defined]
1157+
assert catalog.namespace_exists(test_namespace)
11581158
assert new_metadata.items() <= catalog.load_namespace_properties(test_namespace).items()
11591159
assert remove_metadata.isdisjoint(catalog.load_namespace_properties(test_namespace).keys())
11601160
assert summary.removed == ["key1"]
@@ -1168,13 +1168,13 @@ def test_update_namespace_metadata_removals(
11681168
def test_drop_namespace(catalog: Catalog, table_schema_nested: Schema, test_table_identifier: Identifier) -> None:
11691169
namespace = Catalog.namespace_from(test_table_identifier)
11701170
catalog.create_namespace(namespace)
1171-
assert catalog._namespace_exists(namespace) # type: ignore[attr-defined]
1171+
assert catalog.namespace_exists(namespace)
11721172
catalog.create_table(test_table_identifier, table_schema_nested)
11731173
with pytest.raises(NamespaceNotEmptyError):
11741174
catalog.drop_namespace(namespace)
11751175
catalog.drop_table(test_table_identifier)
11761176
catalog.drop_namespace(namespace)
1177-
assert not catalog._namespace_exists(namespace) # type: ignore[attr-defined]
1177+
assert not catalog.namespace_exists(namespace)
11781178

11791179

11801180
def test_drop_namespace_raises_error_when_namespace_does_not_exist(catalog: Catalog) -> None:

tests/integration/test_catalog.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,10 @@ def test_concurrent_create_transaction(test_catalog: Catalog, test_schema: Schem
410410
@pytest.mark.integration
411411
@pytest.mark.parametrize("test_catalog", CATALOGS)
412412
def test_create_namespace(test_catalog: Catalog, database_name: str) -> None:
413+
assert not test_catalog.namespace_exists(database_name)
414+
413415
test_catalog.create_namespace(database_name)
416+
assert test_catalog.namespace_exists(database_name)
414417
assert (database_name,) in test_catalog.list_namespaces()
415418

416419

0 commit comments

Comments
 (0)