diff --git a/ibis/expr/datatypes/core.py b/ibis/expr/datatypes/core.py index 2a2cfa4eae789..decb29500e34b 100644 --- a/ibis/expr/datatypes/core.py +++ b/ibis/expr/datatypes/core.py @@ -831,13 +831,23 @@ def _pretty_piece(self) -> str: @public class Struct(Parametric, MapSet): - """Structured values.""" + """A nested type with ordered fields of any type, analogous to a dataclass in Python. + + eg a Struct might have a field a of type int64 and a field b of type string. + """ fields: FrozenOrderedDict[str, DataType] scalar = "StructScalar" column = "StructColumn" + def __init__(self, fields, nullable=True): + # We could do this validation in a type annotation, but I thought this + # was common enough to warrant a nicer error message. + if not fields: + raise TypeError("Structs require at least one field") + super().__init__(fields=fields, nullable=nullable) + @classmethod def from_tuples( cls, pairs: Iterable[tuple[str, str | DataType]], nullable: bool = True diff --git a/ibis/expr/datatypes/value.py b/ibis/expr/datatypes/value.py index ad40f9047ecf3..dceccf048d6ab 100644 --- a/ibis/expr/datatypes/value.py +++ b/ibis/expr/datatypes/value.py @@ -42,8 +42,6 @@ def infer(value: Any) -> dt.DataType: @infer.register(collections.OrderedDict) def infer_struct(value: Mapping[str, Any]) -> dt.Struct: """Infer the [`Struct`](./datatypes.qmd#ibis.expr.datatypes.Struct) type of `value`.""" - if not value: - raise TypeError("Empty struct type not supported") fields = {name: infer(val) for name, val in value.items()} return dt.Struct(fields) diff --git a/ibis/tests/expr/test_struct.py b/ibis/tests/expr/test_struct.py index de297a4e4a586..6c87da2102958 100644 --- a/ibis/tests/expr/test_struct.py +++ b/ibis/tests/expr/test_struct.py @@ -22,6 +22,13 @@ def s(): return ibis.table(dict(a="struct"), name="s") +@pytest.mark.parametrize("val", [{}, []]) +@pytest.mark.parametrize("typ", [None, "struct<>"]) +def test_struct_factory_empty(val, typ): + with pytest.raises(TypeError): + ibis.struct(val, type=typ) + + def test_struct_operations(): value = OrderedDict( [