Repository: ibis-project/ibis (Python)

Issue:
bug: Conversion from pyarrow fixed list does not preserve its length
### What happened?

As indicated by `ibis/formats/tests/test_pyarrow.py::test_non_roundtripable_types` it seems that converting pyarrow's FixedSizeListArray (list with fixed size) to ibis does not preserve its length and results in non fixed list with undefined length.

It is simple to fix since ibis supports fixed size list:

```diff
diff --git a/ibis/formats/pyarrow.py b/ibis/formats/pyarrow.py
index 9fd2ad84a..112b762d5 100644
--- a/ibis/formats/pyarrow.py
+++ b/ibis/formats/pyarrow.py
@@ -93,10 +93,12 @@ class PyArrowType(TypeMapper):
         elif (
             pa.types.is_list(typ)
             or pa.types.is_large_list(typ)
-            or pa.types.is_fixed_size_list(typ)
         ):
             value_dtype = cls.to_ibis(typ.value_type, typ.value_field.nullable)
             return dt.Array(value_dtype, nullable=nullable)
+        elif pa.types.is_fixed_size_list(typ):
+            value_dtype = cls.to_ibis(typ.value_type, typ.value_field.nullable)
+            return dt.Array(value_dtype, length=typ.list_size, nullable=nullable)
         elif pa.types.is_struct(typ):
             field_dtypes = {
                 field.name: cls.to_ibis(field.type, field.nullable) for field in typ
@@ -196,7 +198,10 @@ class PyArrowType(TypeMapper):
                 cls.from_ibis(dtype.value_type),
                 nullable=dtype.value_type.nullable,
             )
-            return pa.list_(value_field)
+            if dtype.length is None:
+                return pa.list_(value_field)
+            else:
+                return pa.list_(value_field, dtype.length)
         elif dtype.is_struct():
             fields = [
                 pa.field(name, cls.from_ibis(dtype), nullable=dtype.nullable)
diff --git a/ibis/formats/tests/test_pyarrow.py b/ibis/formats/tests/test_pyarrow.py
index 1ca1aa51c..4d556ab76 100644
--- a/ibis/formats/tests/test_pyarrow.py
+++ b/ibis/formats/tests/test_pyarrow.py
@@ -41,6 +41,7 @@ roundtripable_types = st.deferred(
         | past.binary_type
         | past.timestamp_types
         | st.builds(pa.list_, roundtripable_types)
+        | st.builds(pa.list_, roundtripable_types, st.integers(min_value=1, max_value=(2 ** 31) - 1))
         | past.struct_types(roundtripable_types)

```

### What version of ibis are you using?

Latest

### What backend(s) are you using, if any?

Pyarrow

### Relevant log output

```sh

```

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct


Discussion: LGTM, thanks for the patch! I will incorporate it and add a test.



Fix (patch):
diff --git a/ibis/formats/pyarrow.py b/ibis/formats/pyarrow.py
index 9fd2ad84aede..5ce9205f15f2 100644
--- a/ibis/formats/pyarrow.py
+++ b/ibis/formats/pyarrow.py
@@ -90,11 +90,10 @@ def to_ibis(cls, typ: pa.DataType, nullable=True) -> dt.DataType:
             return dt.Interval(typ.unit, nullable=nullable)
         elif pa.types.is_interval(typ):
             raise ValueError("Arrow interval type is not supported")
-        elif (
-            pa.types.is_list(typ)
-            or pa.types.is_large_list(typ)
-            or pa.types.is_fixed_size_list(typ)
-        ):
+        elif pa.types.is_fixed_size_list(typ):
+            value_dtype = cls.to_ibis(typ.value_type, typ.value_field.nullable)
+            return dt.Array(value_dtype, length=typ.list_size, nullable=nullable)
+        elif pa.types.is_list(typ) or pa.types.is_large_list(typ):
             value_dtype = cls.to_ibis(typ.value_type, typ.value_field.nullable)
             return dt.Array(value_dtype, nullable=nullable)
         elif pa.types.is_struct(typ):
@@ -196,7 +195,10 @@ def from_ibis(cls, dtype: dt.DataType) -> pa.DataType:
                 cls.from_ibis(dtype.value_type),
                 nullable=dtype.value_type.nullable,
             )
-            return pa.list_(value_field)
+            if dtype.length is None:
+                return pa.list_(value_field)
+            else:
+                return pa.list_(value_field, dtype.length)
         elif dtype.is_struct():
             fields = [
                 pa.field(name, cls.from_ibis(dtype), nullable=dtype.nullable)