Skip to content

Commit 26e55be

Browse files
committed
Enable exception suppression during namespace creation
1 parent aa361d1 commit 26e55be

File tree

15 files changed

+159
-18
lines changed

15 files changed

+159
-18
lines changed

pyiceberg/catalog/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,15 +466,21 @@ def _commit_table(self, table_request: CommitTableRequest) -> CommitTableRespons
466466
"""
467467

468468
@abstractmethod
469-
def create_namespace(self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT) -> None:
469+
def create_namespace(
470+
self,
471+
namespace: Union[str, Identifier],
472+
properties: Properties = EMPTY_DICT,
473+
error_if_exists: bool = True,
474+
) -> None:
470475
"""Create a namespace in the catalog.
471476
472477
Args:
473478
namespace (str | Identifier): Namespace identifier.
474479
properties (Properties): A string dictionary of properties for the given namespace.
480+
error_if_exists (bool): If True, raise an error when the namespace already exists. Default is True.
475481
476482
Raises:
477-
NamespaceAlreadyExistsError: If a namespace with the given name already exists.
483+
NamespaceAlreadyExistsError: If a namespace with the given name already exists and error_if_exists is True.
478484
"""
479485

480486
@abstractmethod

pyiceberg/catalog/dynamodb.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,16 +319,22 @@ def rename_table(self, from_identifier: Union[str, Identifier], to_identifier: U
319319

320320
return self.load_table(to_identifier)
321321

322-
def create_namespace(self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT) -> None:
322+
def create_namespace(
323+
self,
324+
namespace: Union[str, Identifier],
325+
properties: Properties = EMPTY_DICT,
326+
error_if_exists: bool = True,
327+
) -> None:
323328
"""Create a namespace in the catalog.
324329
325330
Args:
326-
namespace: Namespace identifier.
327-
properties: A string dictionary of properties for the given namespace.
331+
namespace (str | Identifier): Namespace identifier.
332+
properties (Properties): A string dictionary of properties for the given namespace.
333+
error_if_exists (bool): If True, raise an error when the namespace already exists. Default is True.
328334
329335
Raises:
330336
ValueError: If the identifier is invalid.
331-
AlreadyExistsError: If a namespace with the given name already exists.
337+
NamespaceAlreadyExistsError: If a namespace with the given name already exists and error_if_exists is True.
332338
"""
333339
database_name = self.identifier_to_database(namespace)
334340

@@ -338,6 +344,8 @@ def create_namespace(self, namespace: Union[str, Identifier], properties: Proper
338344
condition_expression=f"attribute_not_exists({DYNAMODB_COL_NAMESPACE})",
339345
)
340346
except ConditionalCheckFailedException as e:
347+
if not error_if_exists:
348+
return
341349
raise NamespaceAlreadyExistsError(f"Database {database_name} already exists") from e
342350

343351
def drop_namespace(self, namespace: Union[str, Identifier]) -> None:

pyiceberg/catalog/glue.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -593,21 +593,29 @@ def rename_table(self, from_identifier: Union[str, Identifier], to_identifier: U
593593

594594
return self.load_table(to_identifier)
595595

596-
def create_namespace(self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT) -> None:
596+
def create_namespace(
597+
self,
598+
namespace: Union[str, Identifier],
599+
properties: Properties = EMPTY_DICT,
600+
error_if_exists: bool = True,
601+
) -> None:
597602
"""Create a namespace in the catalog.
598603
599604
Args:
600-
namespace: Namespace identifier.
601-
properties: A string dictionary of properties for the given namespace.
605+
namespace (str | Identifier): Namespace identifier.
606+
properties (Properties): A string dictionary of properties for the given namespace.
607+
error_if_exists (bool): If True, raise an error when the namespace already exists. Default is True.
602608
603609
Raises:
604610
ValueError: If the identifier is invalid.
605-
AlreadyExistsError: If a namespace with the given name already exists.
611+
NamespaceAlreadyExistsError: If a namespace with the given name already exists and error_if_exists is True.
606612
"""
607613
database_name = self.identifier_to_database(namespace)
608614
try:
609615
self.glue.create_database(DatabaseInput=_construct_database_input(database_name, properties))
610616
except self.glue.exceptions.AlreadyExistsException as e:
617+
if not error_if_exists:
618+
return
611619
raise NamespaceAlreadyExistsError(f"Database {database_name} already exists") from e
612620

613621
def drop_namespace(self, namespace: Union[str, Identifier]) -> None:

pyiceberg/catalog/hive.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -489,16 +489,22 @@ def rename_table(self, from_identifier: Union[str, Identifier], to_identifier: U
489489
raise NoSuchNamespaceError(f"Database does not exists: {to_database_name}") from e
490490
return self.load_table(to_identifier)
491491

492-
def create_namespace(self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT) -> None:
492+
def create_namespace(
493+
self,
494+
namespace: Union[str, Identifier],
495+
properties: Properties = EMPTY_DICT,
496+
error_if_exists: bool = True,
497+
) -> None:
493498
"""Create a namespace in the catalog.
494499
495500
Args:
496501
namespace: Namespace identifier.
497502
properties: A string dictionary of properties for the given namespace.
503+
error_if_exists (bool): If True, raise an error when the namespace already exists. Default is True.
498504
499505
Raises:
500506
ValueError: If the identifier is invalid.
501-
AlreadyExistsError: If a namespace with the given name already exists.
507+
NamespaceAlreadyExistsError: If a namespace with the given name already exists and error_if_exist is True.
502508
"""
503509
database_name = self.identifier_to_database(namespace)
504510
hive_database = HiveDatabase(name=database_name, parameters=properties)
@@ -507,6 +513,8 @@ def create_namespace(self, namespace: Union[str, Identifier], properties: Proper
507513
with self._client as open_client:
508514
open_client.create_database(_annotate_namespace(hive_database, properties))
509515
except AlreadyExistsException as e:
516+
if not error_if_exists:
517+
return
510518
raise NamespaceAlreadyExistsError(f"Database {database_name} already exists") from e
511519

512520
def drop_namespace(self, namespace: Union[str, Identifier]) -> None:

pyiceberg/catalog/noop.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ def rename_table(self, from_identifier: Union[str, Identifier], to_identifier: U
9494
def _commit_table(self, table_request: CommitTableRequest) -> CommitTableResponse:
9595
raise NotImplementedError
9696

97-
def create_namespace(self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT) -> None:
97+
def create_namespace(
98+
self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT, error_if_exists: bool = True
99+
) -> None:
98100
raise NotImplementedError
99101

100102
def drop_namespace(self, namespace: Union[str, Identifier]) -> None:

pyiceberg/catalog/rest.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,13 +708,30 @@ def _commit_table(self, table_request: CommitTableRequest) -> CommitTableRespons
708708
return CommitTableResponse(**response.json())
709709

710710
@retry(**_RETRY_ARGS)
711-
def create_namespace(self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT) -> None:
711+
def create_namespace(
712+
self,
713+
namespace: Union[str, Identifier],
714+
properties: Properties = EMPTY_DICT,
715+
error_if_exists: bool = True,
716+
) -> None:
717+
"""Create a namespace in the catalog.
718+
719+
Args:
720+
namespace (str | Identifier): Namespace identifier.
721+
properties (Properties): A string dictionary of properties for the given namespace.
722+
error_if_exists (bool): If True, raise an error when the namespace already exists. Default is True.
723+
724+
Raises:
725+
NamespaceAlreadyExistsError: If a namespace with the given name already exists and error_if_exists is True.
726+
"""
712727
namespace_tuple = self._check_valid_namespace_identifier(namespace)
713728
payload = {"namespace": namespace_tuple, "properties": properties}
714729
response = self._session.post(self.url(Endpoints.create_namespace), json=payload)
715730
try:
716731
response.raise_for_status()
717732
except HTTPError as exc:
733+
if exc.response.status_code == 409 and not error_if_exists:
734+
return
718735
self._handle_non_200_response(exc, {404: NoSuchNamespaceError, 409: NamespaceAlreadyExistsError})
719736

720737
@retry(**_RETRY_ARGS)

pyiceberg/catalog/sql.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,20 +452,28 @@ def _namespace_exists(self, identifier: Union[str, Identifier]) -> bool:
452452
return True
453453
return False
454454

455-
def create_namespace(self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT) -> None:
455+
def create_namespace(
456+
self,
457+
namespace: Union[str, Identifier],
458+
properties: Properties = EMPTY_DICT,
459+
error_if_exists: bool = True,
460+
) -> None:
456461
"""Create a namespace in the catalog.
457462
458463
Args:
459464
namespace (str | Identifier): Namespace identifier.
460465
properties (Properties): A string dictionary of properties for the given namespace.
466+
error_if_exists (bool): If True, raise an error when the namespace already exists. Default is True.
461467
462468
Raises:
463-
NamespaceAlreadyExistsError: If a namespace with the given name already exists.
469+
NamespaceAlreadyExistsError: If a namespace with the given name already exists and error_if_exists is True.
464470
"""
465471
if not properties:
466472
properties = IcebergNamespaceProperties.NAMESPACE_MINIMAL_PROPERTIES
467473
database_name = self.identifier_to_database(namespace)
468474
if self._namespace_exists(database_name):
475+
if not error_if_exists:
476+
return
469477
raise NamespaceAlreadyExistsError(f"Database {database_name} already exists")
470478

471479
create_properties = properties if properties else IcebergNamespaceProperties.NAMESPACE_MINIMAL_PROPERTIES

tests/catalog/integration_test_dynamodb.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ def test_create_duplicate_namespace(test_catalog: Catalog, database_name: str) -
184184
test_catalog.create_namespace(database_name)
185185

186186

187+
def test_create_duplicate_namespace_when_error_if_exists_is_false(test_catalog: Catalog, database_name: str) -> None:
188+
test_catalog.create_namespace(database_name)
189+
test_catalog.create_namespace(database_name, error_if_exists=False)
190+
191+
187192
def test_create_namespace_with_comment_and_location(test_catalog: Catalog, database_name: str) -> None:
188193
test_location = get_s3_path(get_bucket_name(), database_name)
189194
test_properties = {

tests/catalog/integration_test_glue.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,11 @@ def test_create_duplicate_namespace(test_catalog: Catalog, database_name: str) -
291291
test_catalog.create_namespace(database_name)
292292

293293

294+
def test_create_duplicate_namespace_when_error_if_exists_is_false(test_catalog: Catalog, database_name: str) -> None:
295+
test_catalog.create_namespace(database_name)
296+
test_catalog.create_namespace(database_name, error_if_exists=False)
297+
298+
294299
def test_create_namespace_with_comment_and_location(test_catalog: Catalog, database_name: str) -> None:
295300
test_location = get_s3_path(get_bucket_name(), database_name)
296301
test_properties = {

tests/catalog/test_base.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,13 @@ def rename_table(self, from_identifier: Union[str, Identifier], to_identifier: U
195195
)
196196
return self.__tables[to_identifier]
197197

198-
def create_namespace(self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT) -> None:
198+
def create_namespace(
199+
self, namespace: Union[str, Identifier], properties: Properties = EMPTY_DICT, error_if_exists: bool = True
200+
) -> None:
199201
namespace = Catalog.identifier_to_tuple(namespace)
200202
if namespace in self.__namespaces:
203+
if not error_if_exists:
204+
return
201205
raise NamespaceAlreadyExistsError(f"Namespace already exists: {namespace}")
202206
else:
203207
self.__namespaces[namespace] = properties if properties else {}
@@ -541,6 +545,15 @@ def test_create_namespace_raises_error_on_existing_namespace(catalog: InMemoryCa
541545
catalog.create_namespace(TEST_TABLE_NAMESPACE, TEST_TABLE_PROPERTIES)
542546

543547

548+
def test_create_namespace_does_not_raises_error_on_existing_namespace_when_error_if_exists_is_false(
549+
catalog: InMemoryCatalog,
550+
) -> None:
551+
# Given
552+
catalog.create_namespace(TEST_TABLE_NAMESPACE, TEST_TABLE_PROPERTIES)
553+
# When
554+
catalog.create_namespace(TEST_TABLE_NAMESPACE, TEST_TABLE_PROPERTIES, error_if_exists=False)
555+
556+
544557
def test_get_namespace_metadata_raises_error_when_namespace_does_not_exist(catalog: InMemoryCatalog) -> None:
545558
with pytest.raises(NoSuchNamespaceError, match=NO_SUCH_NAMESPACE_ERROR):
546559
catalog.load_namespace_properties(TEST_TABLE_NAMESPACE)

0 commit comments

Comments
 (0)