Improve CLI describe entity detection (#3934) · apache/iceberg-python@562d3af · GitHub
Skip to content

Commit 562d3af

Browse files
kevinjqliuCopilot
andauthored
Improve CLI describe entity detection (#3934)
* Improve CLI describe entity detection Resolve namespace and table candidates before rendering output so ambiguous identifiers require explicit disambiguation without partial descriptions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Remove duplicate ambiguity assertion Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix BigQuery namespace miss handling Report unsupported multipart namespace identifiers as NoSuchNamespaceError so CLI describe can continue to the table candidate without hiding unexpected catalog errors. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Apply batched suggestions from code review Co-authored-by: Kevin Liu <kevinjqliu@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 13d085e commit 562d3af

4 files changed

Lines changed: 97 additions & 28 deletions

File tree

‎pyiceberg/catalog/bigquery_metastore.py‎

Lines changed: 1 addition & 1 deletion

‎pyiceberg/cli/console.py‎

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131
from pyiceberg.cli.output import ConsoleOutput, JsonOutput, Output
3232
from pyiceberg.exceptions import NoSuchNamespaceError, NoSuchPropertyException, NoSuchTableError
3333
from pyiceberg.io import WAREHOUSE
34-
from pyiceberg.table import TableProperties
34+
from pyiceberg.table import Table, TableProperties
3535
from pyiceberg.table.refs import SnapshotRef, SnapshotRefType
36+
from pyiceberg.typedef import Properties
3637
from pyiceberg.utils.properties import property_as_int
3738

3839

@@ -142,38 +143,62 @@ def list(ctx: Context, parent: str | None) -> None: # pylint: disable=redefined
142143

143144

144145
@run.command()
145-
@click.option("--entity", type=click.Choice(["any", "namespace", "table"]), default="any")
146+
@click.option(
147+
"--entity",
148+
type=click.Choice(["any", "namespace", "table"]),
149+
default="any",
150+
help="Entity type. 'any' auto-detects and requires --entity when ambiguous.",
151+
)
146152
@click.argument("identifier")
147153
@click.pass_context
148154
@catch_exception()
149-
def describe(ctx: Context, entity: Literal["name", "namespace", "table"], identifier: str) -> None:
155+
def describe(ctx: Context, entity: Literal["any", "namespace", "table"], identifier: str) -> None:
150156
"""Describe a namespace or a table."""
151157
catalog, output = _catalog_and_output(ctx)
152158
identifier_tuple = Catalog.identifier_to_tuple(identifier)
153159

154-
is_namespace = False
155-
if entity in {"namespace", "any"} and len(identifier_tuple) > 0:
156-
try:
157-
namespace_properties = catalog.load_namespace_properties(identifier_tuple)
158-
output.describe_properties(namespace_properties)
159-
is_namespace = True
160-
except NoSuchNamespaceError as exc:
161-
if entity != "any" or len(identifier_tuple) == 1: # type: ignore
162-
raise exc
163-
164-
is_table = False
165-
if entity in {"table", "any"} and len(identifier_tuple) > 1:
166-
try:
167-
catalog_table = catalog.load_table(identifier)
168-
output.describe_table(catalog_table)
169-
is_table = True
170-
except NoSuchTableError as exc:
171-
if entity != "any":
172-
raise exc
173-
174-
if is_namespace is False and is_table is False:
160+
if entity == "namespace":
161+
output.describe_properties(catalog.load_namespace_properties(identifier_tuple))
162+
return
163+
if entity == "table":
164+
output.describe_table(catalog.load_table(identifier))
165+
return
166+
167+
# For the default "any" entity, auto-detect the entity type.
168+
if len(identifier_tuple) == 1:
169+
output.describe_properties(catalog.load_namespace_properties(identifier_tuple))
170+
return
171+
172+
matches: tuple[str, ...] = ()
173+
namespace_properties: Properties | None = None
174+
catalog_table: Table | None = None
175+
176+
try:
177+
namespace_properties = catalog.load_namespace_properties(identifier_tuple)
178+
matches += ("namespace",)
179+
except NoSuchNamespaceError:
180+
pass
181+
182+
try:
183+
catalog_table = catalog.load_table(identifier)
184+
matches += ("table",)
185+
except NoSuchTableError:
186+
pass
187+
188+
if len(matches) > 1:
189+
raise ValueError(
190+
f"Identifier {identifier} matches multiple entity types: {', '.join(matches)}. Use --entity to disambiguate."
191+
)
192+
if not matches:
175193
raise NoSuchTableError(f"Table or namespace does not exist: {identifier}")
176194

195+
if matches[0] == "namespace":
196+
assert namespace_properties is not None
197+
output.describe_properties(namespace_properties)
198+
else:
199+
assert catalog_table is not None
200+
output.describe_table(catalog_table)
201+
177202

178203
@run.command()
179204
@click.argument("identifier")

‎tests/catalog/test_bigquery_metastore.py‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
import os
1818
from unittest.mock import MagicMock
1919

20+
import pytest
2021
from google.api_core.exceptions import NotFound
2122
from google.cloud.bigquery import Dataset, DatasetReference, Table, TableReference
2223
from google.cloud.bigquery.external_config import ExternalCatalogDatasetOptions, ExternalCatalogTableOptions
2324
from pytest_mock import MockFixture
2425

2526
from pyiceberg.catalog.bigquery_metastore import ICEBERG_TABLE_TYPE_VALUE, TABLE_TYPE_PROP, BigQueryMetastoreCatalog
26-
from pyiceberg.exceptions import NoSuchTableError
27+
from pyiceberg.exceptions import NoSuchNamespaceError, NoSuchTableError
2728
from pyiceberg.schema import Schema
2829

2930

@@ -178,3 +179,15 @@ def test_list_namespaces(mocker: MockFixture) -> None:
178179
assert ("dataset1",) in namespaces
179180
assert ("dataset2",) in namespaces
180181
client_mock.list_datasets.assert_called_once()
182+
183+
184+
def test_load_namespace_properties_rejects_multipart_namespace(mocker: MockFixture) -> None:
185+
client_mock = MagicMock()
186+
mocker.patch("pyiceberg.catalog.bigquery_metastore.Client", return_value=client_mock)
187+
mocker.patch.dict(os.environ, values={"PYICEBERG_LEGACY_CURRENT_SNAPSHOT_ID": "True"})
188+
catalog = BigQueryMetastoreCatalog("test_catalog", **{"gcp.bigquery.project-id": "my-project"})
189+
190+
with pytest.raises(NoSuchNamespaceError, match="hierarchical namespaces are not supported"):
191+
catalog.load_namespace_properties(("dataset", "table"))
192+
193+
client_mock.get_dataset.assert_not_called()

‎tests/cli/test_console.py‎

Lines changed: 33 additions & 2 deletions

0 commit comments

Comments
 (0)