From b2ae763254dfc4237e2b281dab192aad12fd8576 Mon Sep 17 00:00:00 2001 From: Armaan Sandhu <74664101+Ar-maan05@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:28:48 +0530 Subject: [PATCH] fix(python): raise clear TypeError for bare List/Tuple in pydantic schema conversion (#3511) Closes #3502 ## Problem A bare, unparameterised `typing.List` / `typing.Tuple` field crashes `to_arrow_schema` with an opaque `AttributeError: __args__`: ```python from typing import Tuple from lancedb.pydantic import LanceModel class Doc(LanceModel): items: Tuple Doc.to_arrow_schema() # AttributeError: __args__ ``` In `_py_type_to_arrow_type`, the branch `elif getattr(py_type, "__origin__", None) in (list, tuple)` is taken for a bare generic (its `__origin__` is `list / tuple`), but the next line reads `py_type.__args__[0]`, and a bare generic has no `__args__`. Other unsupported types (e.g. `Dict[str, int]`) correctly raise a clear `TypeError`, so this case is inconsistent. Fix Guard the element-type lookup with `getattr(py_type, "__args__", None)` and raise a clear `TypeError` when it is missing, matching the existing behavior for other unsupported types. Bare builtin list / tuple are unaffected (their `__origin__` is `None`, so they already fall through to the existing `TypeError`). Testing - Added `test_bare_generic_raises_type_error` covering both `List` and `Tuple`. - ruff format and ruff check clean. --- python/python/lancedb/pydantic.py | 13 ++++++++++++- python/python/tests/test_pydantic.py | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/python/python/lancedb/pydantic.py b/python/python/lancedb/pydantic.py index 8b1f991f9..c4dedc0e6 100644 --- a/python/python/lancedb/pydantic.py +++ b/python/python/lancedb/pydantic.py @@ -275,7 +275,18 @@ def _py_type_to_arrow_type(py_type: Type[Any], field: FieldInfo) -> pa.DataType: tz = get_extras(field, "tz") return pa.timestamp("us", tz=tz) elif getattr(py_type, "__origin__", None) in (list, tuple): - child = py_type.__args__[0] + # A bare, unparameterised ``typing.List`` / ``typing.Tuple`` matches this + # branch (its ``__origin__`` is ``list`` / ``tuple``) but has no + # ``__args__``, so we cannot infer the element type. Raise a clear + # ``TypeError`` instead of crashing with an opaque ``AttributeError``. + args = getattr(py_type, "__args__", None) + if not args: + raise TypeError( + "Converting Pydantic type to Arrow Type: unsupported type " + f"{py_type}. Specify the element type, e.g. List[int] instead " + "of a bare List." + ) + child = args[0] return _pydantic_list_child_to_arrow(child, field) raise TypeError( f"Converting Pydantic type to Arrow Type: unsupported type {py_type}." diff --git a/python/python/tests/test_pydantic.py b/python/python/tests/test_pydantic.py index 701bbef5a..e1d533784 100644 --- a/python/python/tests/test_pydantic.py +++ b/python/python/tests/test_pydantic.py @@ -188,6 +188,18 @@ def test_nested_struct_list(): assert schema == expect_schema +def test_bare_generic_raises_type_error(): + # A bare, unparameterised List/Tuple has no element type to map to Arrow. + # It should raise a clear TypeError, not crash with AttributeError: __args__. + for bare in (List, Tuple): + + class TestModel(pydantic.BaseModel): + items: bare + + with pytest.raises(TypeError, match="unsupported type"): + pydantic_to_schema(TestModel) + + def test_nested_struct_list_optional(): class SplitInfo(pydantic.BaseModel): start_frame: int