Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/src/lance_namespace_impls/glue.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ def list_tables(self, request: ListTablesRequest) -> ListTablesResponse:

def describe_table(self, request: DescribeTableRequest) -> DescribeTableResponse:
"""Describe a table."""
if not isinstance(request, DescribeTableRequest):
request = DescribeTableRequest.from_dict(dict(request))
if request.load_detailed_metadata:
raise RuntimeError(
"load_detailed_metadata=true is not supported for this implementation"
Expand Down
2 changes: 2 additions & 0 deletions python/src/lance_namespace_impls/hive2.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ def describe_table(self, request: DescribeTableRequest) -> DescribeTableResponse

Only load_detailed_metadata=false is supported.
"""
if not isinstance(request, DescribeTableRequest):
request = DescribeTableRequest.from_dict(dict(request))
if request.load_detailed_metadata:
raise ValueError(
"load_detailed_metadata=true is not supported for this implementation"
Expand Down
2 changes: 2 additions & 0 deletions python/src/lance_namespace_impls/hive3.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ def describe_table(self, request: DescribeTableRequest) -> DescribeTableResponse

Only load_detailed_metadata=false is supported.
"""
if not isinstance(request, DescribeTableRequest):
request = DescribeTableRequest.from_dict(dict(request))
if request.load_detailed_metadata:
raise ValueError(
"load_detailed_metadata=true is not supported for this implementation"
Expand Down
2 changes: 2 additions & 0 deletions python/src/lance_namespace_impls/iceberg.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ def describe_table(self, request: DescribeTableRequest) -> DescribeTableResponse
The first element of request.id is the warehouse.
Middle elements are the namespace, last element is the table name.
"""
if not isinstance(request, DescribeTableRequest):
request = DescribeTableRequest.from_dict(dict(request))
if request.load_detailed_metadata:
raise InvalidInputException(
"load_detailed_metadata=true is not supported for this implementation"
Expand Down
2 changes: 2 additions & 0 deletions python/src/lance_namespace_impls/polaris.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ def describe_table(self, request: DescribeTableRequest) -> DescribeTableResponse

Only load_detailed_metadata=false is supported.
"""
if not isinstance(request, DescribeTableRequest):
request = DescribeTableRequest.from_dict(dict(request))
if request.load_detailed_metadata:
raise InvalidInputException(
"load_detailed_metadata=true is not supported for this implementation"
Expand Down
2 changes: 2 additions & 0 deletions python/src/lance_namespace_impls/unity.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ def declare_table(self, request: DeclareTableRequest) -> DeclareTableResponse:

def describe_table(self, request: DescribeTableRequest) -> DescribeTableResponse:
"""Describe a table."""
if not isinstance(request, DescribeTableRequest):
request = DescribeTableRequest.from_dict(dict(request))
if request.load_detailed_metadata:
raise InvalidInputException(
"load_detailed_metadata=true is not supported for this implementation"
Expand Down
23 changes: 23 additions & 0 deletions python/tests/test_glue.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,29 @@ def test_describe_table(self, glue_namespace):

assert response.location == "s3://bucket/table.lance"

def test_describe_table_accepts_dict_request(self, glue_namespace):
"""pylance's Rust namespace bridge passes the request as a plain dict
(DictWithModelDump), not a DescribeTableRequest model. describe_table must
coerce it instead of raising AttributeError on attribute access. See #181."""
glue_namespace.glue.get_table.return_value = {
"Table": {
"Name": "test_table",
"Parameters": {"table_type": "LANCE"},
"StorageDescriptor": {"Location": "s3://bucket/table.lance"},
}
}

class DictWithModelDump(dict):
def model_dump(self):
return dict(self)

request = DictWithModelDump(
{"id": ["test_db", "test_table"], "load_detailed_metadata": False}
)
response = glue_namespace.describe_table(request)

assert response.location == "s3://bucket/table.lance"

def test_describe_table_not_lance(self, glue_namespace):
"""Test describing a non-Lance table."""
glue_namespace.glue.get_table.return_value = {
Expand Down
26 changes: 26 additions & 0 deletions python/tests/test_hive2.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,32 @@ def test_describe_table(self, hive_namespace, mock_hive_client):

mock_client_instance.get_table.assert_called_once_with("test_db", "test_table")

def test_describe_table_accepts_dict_request(
self, hive_namespace, mock_hive_client
):
"""pylance's Rust namespace bridge passes the request as a plain dict
(DictWithModelDump), not a DescribeTableRequest model. describe_table must
coerce it instead of raising AttributeError on attribute access. See #181."""
mock_table = MagicMock()
mock_table.sd.location = "/tmp/warehouse/test_db.db/test_table"
mock_table.owner = "table_owner"
mock_table.parameters = {"table_type": "lance", "version": "42"}

mock_client_instance = MagicMock()
mock_client_instance.get_table.return_value = mock_table
mock_hive_client.__enter__.return_value = mock_client_instance

class DictWithModelDump(dict):
def model_dump(self):
return dict(self)

request = DictWithModelDump(
{"id": ["test_db", "test_table"], "load_detailed_metadata": False}
)
response = hive_namespace.describe_table(request)

assert response.location == "/tmp/warehouse/test_db.db/test_table"

def test_deregister_table(self, hive_namespace, mock_hive_client):
"""Test deregistering a table without deleting data."""
mock_table = MagicMock()
Expand Down
25 changes: 25 additions & 0 deletions python/tests/test_hive3.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,31 @@ def test_describe_table(self, hive_namespace, mock_hive_client):

mock_client_instance.get_table.assert_called_once_with("test_db", "test_table")

def test_describe_table_accepts_dict_request(
self, hive_namespace, mock_hive_client
):
"""pylance's Rust namespace bridge passes the request as a plain dict
(DictWithModelDump), not a DescribeTableRequest model. describe_table must
coerce it instead of raising AttributeError on attribute access. See #181."""
mock_table = MagicMock()
mock_table.sd.location = "/tmp/warehouse/test_db/test_table"
mock_table.parameters = {"table_type": "lance", "version": "42"}

mock_client_instance = MagicMock()
mock_client_instance.get_table.return_value = mock_table
mock_hive_client.__enter__.return_value = mock_client_instance

class DictWithModelDump(dict):
def model_dump(self):
return dict(self)

request = DictWithModelDump(
{"id": ["hive", "test_db", "test_table"], "load_detailed_metadata": False}
)
response = hive_namespace.describe_table(request)

assert response.location == "/tmp/warehouse/test_db/test_table"

def test_deregister_table(self, hive_namespace, mock_hive_client):
"""Test deregistering a table with 3-level identifier."""
mock_table = MagicMock()
Expand Down
34 changes: 34 additions & 0 deletions python/tests/test_iceberg.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,40 @@ def test_describe_table(self, mock_rest_client_class):
self.assertEqual(response.properties, {"table_type": "lance", "key": "value"})
self.assertFalse(response.managed_versioning)

@patch("lance_namespace_impls.iceberg.RestClient")
def test_describe_table_accepts_dict_request(self, mock_rest_client_class):
"""pylance's Rust namespace bridge passes the request as a plain dict
(DictWithModelDump), not a DescribeTableRequest model. describe_table must
coerce it instead of raising AttributeError on attribute access. See #181."""
mock_client = MagicMock()
mock_rest_client_class.return_value = mock_client

mock_client.get.side_effect = [
{"defaults": {"prefix": "warehouse1"}},
{
"metadata": {
"location": "/data/lance/ns/table",
"properties": {"table_type": "lance", "key": "value"},
}
},
]

namespace = IcebergNamespace(**self.properties)

class DictWithModelDump(dict):
def model_dump(self):
return dict(self)

request = DictWithModelDump(
{
"id": ["warehouse1", "test_namespace", "test_table"],
"load_detailed_metadata": False,
}
)
response = namespace.describe_table(request)

self.assertEqual(response.location, "/data/lance/ns/table")

@patch("lance_namespace_impls.iceberg.RestClient")
def test_describe_table_not_lance(self, mock_rest_client_class):
"""Test describing a table that is not a Lance table."""
Expand Down
32 changes: 32 additions & 0 deletions python/tests/test_polaris.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,38 @@ def test_describe_table(self, mock_rest_client_class):
"/polaris/v1/test_catalog/namespaces/test_namespace/generic-tables/test_table"
)

@patch("lance_namespace_impls.polaris.RestClient")
def test_describe_table_accepts_dict_request(self, mock_rest_client_class):
"""pylance's Rust namespace bridge passes the request as a plain dict
(DictWithModelDump), not a DescribeTableRequest model. describe_table must
coerce it instead of raising AttributeError on attribute access. See #181."""
mock_client = MagicMock()
mock_rest_client_class.return_value = mock_client

mock_client.get.return_value = {
"table": {
"format": "lance",
"base-location": "/data/lance/ns/table",
"properties": {"key": "value"},
}
}

namespace = PolarisNamespace(**self.properties)

class DictWithModelDump(dict):
def model_dump(self):
return dict(self)

request = DictWithModelDump(
{
"id": ["test_catalog", "test_namespace", "test_table"],
"load_detailed_metadata": False,
}
)
response = namespace.describe_table(request)

self.assertEqual(response.location, "/data/lance/ns/table")

@patch("lance_namespace_impls.polaris.RestClient")
def test_describe_table_not_lance(self, mock_rest_client_class):
"""Test describing a table that is not a Lance table."""
Expand Down
38 changes: 38 additions & 0 deletions python/tests/test_unity.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,44 @@ def test_describe_table(self, mock_rest_client_class):
response.location, "/data/lance/test_catalog/test_schema/test_table"
)

@patch("lance_namespace_impls.unity.RestClient")
def test_describe_table_accepts_dict_request(self, mock_rest_client_class):
"""pylance's Rust namespace bridge passes the request as a plain dict
(DictWithModelDump), not a DescribeTableRequest model. describe_table must
coerce it instead of raising AttributeError on attribute access. See #181."""
mock_client = MagicMock()
mock_rest_client_class.return_value = mock_client

mock_table_info = TableInfo(
name="test_table",
catalog_name="test_catalog",
schema_name="test_schema",
table_type="EXTERNAL",
data_source_format="TEXT",
columns=[],
storage_location="/data/lance/test_catalog/test_schema/test_table",
properties={"table_type": "lance"},
)
mock_client.get.return_value = mock_table_info

namespace = UnityNamespace(**self.properties)

class DictWithModelDump(dict):
def model_dump(self):
return dict(self)

request = DictWithModelDump(
{
"id": ["test_catalog", "test_schema", "test_table"],
"load_detailed_metadata": False,
}
)
response = namespace.describe_table(request)

self.assertEqual(
response.location, "/data/lance/test_catalog/test_schema/test_table"
)

def test_arrow_type_conversion(self):
"""Test Arrow type to Unity type conversion."""
namespace = UnityNamespace(**self.properties)
Expand Down
Loading