Copilot commented on code in PR #48622:
URL: https://github.com/apache/arrow/pull/48622#discussion_r4376308981


##########
python/pyarrow-stubs/pyarrow/_stubs_typing.pyi:
##########
@@ -0,0 +1,138 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import datetime as dt
+
+from _typeshed import (
+    SupportsDunderGE,
+    SupportsDunderGT,
+    SupportsDunderLE,
+    SupportsDunderLT,
+)
+from collections.abc import Collection, Container, Iterator, Sequence, Sized
+from decimal import Decimal
+from typing import Any, Literal, Protocol, TypeAlias, TypeVar
+from typing_extensions import TypeAliasType
+
+import numpy as np
+
+from numpy.typing import NDArray
+
+from pyarrow import lib
+from pyarrow.lib import ChunkedArray
+
+ArrayLike: TypeAlias = Any
+ScalarLike: TypeAlias = Any
+Order: TypeAlias = Literal["ascending", "descending"]
+JoinType: TypeAlias = Literal[
+    "left semi",
+    "right semi",
+    "left anti",
+    "right anti",
+    "inner",
+    "left outer",
+    "right outer",
+    "full outer",
+]
+Compression: TypeAlias = Literal[
+    "gzip", "bz2", "brotli", "lz4", "lz4_frame", "lz4_raw", "zstd", "snappy"
+]
+NullEncoding: TypeAlias = Literal["mask", "encode"]
+NullSelectionBehavior: TypeAlias = Literal["drop", "emit_null"]
+TimeUnit: TypeAlias = Literal["s", "ms", "us", "ns"]
+
+IntegerType: TypeAlias = (
+    lib.Int8Type
+    | lib.Int16Type
+    | lib.Int32Type
+    | lib.Int64Type
+    | lib.UInt8Type
+    | lib.UInt16Type
+    | lib.UInt32Type
+    | lib.UInt64Type
+)
+PyScalar: TypeAlias = (
+    bool
+    | int
+    | float
+    | Decimal
+    | str
+    | bytes
+    | dt.date
+    | dt.datetime
+    | dt.time
+    | dt.timedelta
+)
+NumpyScalar: TypeAlias = np.generic[Any]
+
+_PyScalarT_co = TypeVar("_PyScalarT_co", bound=PyScalar, covariant=True)
+_NumpyScalarT_co = TypeVar("_NumpyScalarT_co", bound=NumpyScalar, 
covariant=True)
+_DataTypeT_co = TypeVar("_DataTypeT_co", bound=lib.DataType, covariant=True)
+IntoArray = TypeAliasType(
+    "IntoArray",
+    Sequence[_PyScalarT_co | None]
+    | NDArray[_NumpyScalarT_co]
+    | lib.Array[lib.Scalar[_DataTypeT_co]]
+    | ChunkedArray[Any],
+    type_params=(_PyScalarT_co, _NumpyScalarT_co, _DataTypeT_co),
+)
+
+Mask: TypeAlias = IntoArray[bool, np.bool_, lib.BoolType]
+Indices: TypeAlias = IntoArray[int, np.integer[Any], IntegerType]
+
+_T = TypeVar("_T")
+_V = TypeVar("_V", covariant=True)
+
+SingleOrList: TypeAlias = list[_T] | _T
+
+class SupportsDunderEQ(Protocol):
+    def __eq__(self, other: object, /) -> bool: ...
+
+FilterTuple: TypeAlias = (
+    tuple[str, Literal["=", "==", "!="], SupportsDunderEQ]
+    | tuple[str, Literal["<"], SupportsDunderLT[Any]]
+    | tuple[str, Literal[">"], SupportsDunderGT[Any]]
+    | tuple[str, Literal["<="], SupportsDunderLE[Any]]
+    | tuple[str, Literal[">="], SupportsDunderGE[Any]]
+    | tuple[str, Literal["in", "not in"], Collection[Any]]
+    | tuple[str, str, Any]  # Allow general str for operator to avoid type 
errors
+)
+
+class Buffer(Protocol): ...
+class SupportsPyBuffer(Protocol): ...
+
+class SupportsArrowStream(Protocol):
+    def __arrow_c_stream__(self, requested_schema: Any = None, /) -> Any: ...
+
+class SupportsPyArrowArray(Protocol):
+    def __arrow_array__(self, type: Any = None, /) -> Any: ...

Review Comment:
   The array dispatcher calls `obj.__arrow_array__(type=type)` 
(`array.pxi:124`), so this parameter must accept a keyword. Making it 
positional-only allows implementations that fail at runtime and excludes valid 
keyword-only implementations. Declare `type` keyword-only in the protocol so it 
matches the actual calling convention.



##########
python/pyarrow-stubs/pyarrow/_types.pyi:
##########
@@ -0,0 +1,768 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import datetime as dt  # noqa: F401
+
+from collections.abc import Iterable, Iterator, Mapping, Sequence
+from decimal import Decimal  # noqa: F401
+from typing import Any, Generic, Literal, Protocol, TypeAlias, overload
+
+import numpy as np
+import pandas as pd  # type: ignore[import-not-found,import-untyped]  # 
pyright: ignore[reportMissingImports,reportMissingTypeStubs]  # noqa: E501
+from typing_extensions import Self, TypeVar, deprecated
+
+from pyarrow._stubs_typing import SupportsArrowSchema, TimeUnit
+from pyarrow.lib import (  # noqa: F401
+    Array,
+    Buffer,
+    ChunkedArray,
+    ExtensionArray,
+    ExtensionScalar,
+    MemoryPool,
+    MonthDayNano,
+    Table,
+)
+
+class _Weakrefable: ...
+class _Metadata(_Weakrefable): ...
+
+class DataType(_Weakrefable):
+    def field(self, i: int) -> Field[Any]: ...
+    @property
+    def id(self) -> int: ...
+    @property
+    def bit_width(self) -> int: ...
+    @property
+    def byte_width(self) -> int: ...
+    @property
+    def num_fields(self) -> int: ...
+    @property
+    def num_buffers(self) -> int: ...
+    @property
+    def has_variadic_buffers(self) -> bool: ...
+    def __hash__(self) -> int: ...
+    def equals(
+        self, other: DataType | str, *, check_metadata: bool = False
+    ) -> bool: ...
+    def to_pandas_dtype(self) -> _PandasDtype: ...
+    def _export_to_c(self, out_ptr: int) -> None: ...
+    @classmethod
+    def _import_from_c(cls, in_ptr: int) -> Self: ...
+    def __arrow_c_schema__(self) -> Any: ...
+    @classmethod
+    def _import_from_c_capsule(cls, schema: Any) -> Self: ...
+
+_AsPyType = TypeVar("_AsPyType")
+_DataTypeT = TypeVar("_DataTypeT", bound=DataType)
+_DataTypeT_co = TypeVar("_DataTypeT_co", bound=DataType, covariant=True)
+_Size = TypeVar("_Size", bound=int, default=int)
+_PandasDtype: TypeAlias = (
+    type[np.generic] | np.dtype[Any] | pd.api.extensions.ExtensionDtype
+)
+
+class _BasicDataType(DataType, Generic[_AsPyType]): ...
+class NullType(_BasicDataType[None]): ...
+class BoolType(_BasicDataType[bool]): ...
+class UInt8Type(_BasicDataType[int]): ...
+class Int8Type(_BasicDataType[int]): ...
+class UInt16Type(_BasicDataType[int]): ...
+class Int16Type(_BasicDataType[int]): ...
+class UInt32Type(_BasicDataType[int]): ...
+class Int32Type(_BasicDataType[int]): ...
+class UInt64Type(_BasicDataType[int]): ...
+class Int64Type(_BasicDataType[int]): ...
+class Float16Type(_BasicDataType[float]): ...
+class Float32Type(_BasicDataType[float]): ...
+class Float64Type(_BasicDataType[float]): ...
+class Date32Type(_BasicDataType[dt.date]): ...
+class Date64Type(_BasicDataType[dt.date]): ...
+class MonthDayNanoIntervalType(_BasicDataType[MonthDayNano]): ...
+class StringType(_BasicDataType[str]): ...
+class LargeStringType(_BasicDataType[str]): ...
+class StringViewType(_BasicDataType[str]): ...
+class BinaryType(_BasicDataType[bytes]): ...
+class LargeBinaryType(_BasicDataType[bytes]): ...
+class BinaryViewType(_BasicDataType[bytes]): ...
+
+_Unit = TypeVar("_Unit", bound=TimeUnit, default=Literal["us"])
+_Tz = TypeVar("_Tz", str, None, default=None)
+
+class TimestampType(_BasicDataType[dt.datetime], Generic[_Unit, _Tz]):
+    @property
+    def unit(self) -> _Unit: ...
+    @property
+    def tz(self) -> _Tz: ...
+
+_Time32Unit = TypeVar("_Time32Unit", bound=Literal["s", "ms"])
+
+class Time32Type(_BasicDataType[dt.time], Generic[_Time32Unit]):
+    @property
+    def unit(self) -> _Time32Unit: ...
+
+_Time64Unit = TypeVar("_Time64Unit", bound=Literal["us", "ns"])
+
+class Time64Type(_BasicDataType[dt.time], Generic[_Time64Unit]):
+    @property
+    def unit(self) -> _Time64Unit: ...
+
+class DurationType(_BasicDataType[dt.timedelta], Generic[_Unit]):
+    @property
+    def unit(self) -> _Unit: ...
+
+_FixedSizeBinaryAsPyType = TypeVar("_FixedSizeBinaryAsPyType", default=bytes)
+
+class FixedSizeBinaryType(_BasicDataType[_FixedSizeBinaryAsPyType]): ...
+
+_Precision = TypeVar("_Precision", bound=int, default=int)
+_Scale = TypeVar("_Scale", bound=int, default=int)
+_Precision_co = TypeVar("_Precision_co", bound=int, default=int, 
covariant=True)
+_Scale_co = TypeVar("_Scale_co", bound=int, default=int, covariant=True)
+
+class _HasPrecisionScale(Protocol[_Precision_co, _Scale_co]):
+    @property
+    def precision(self) -> _Precision_co: ...
+    @property
+    def scale(self) -> _Scale_co: ...
+
+class Decimal32Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal64Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal128Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal256Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+
+class ListType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class LargeListType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class ListViewType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class LargeListViewType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class FixedSizeListType(DataType, Generic[_DataTypeT_co, _Size]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+    @property
+    def list_size(self) -> _Size: ...
+
+class DictionaryMemo(_Weakrefable): ...
+
+_IndexT = TypeVar(
+    "_IndexT",
+    UInt8Type,
+    Int8Type,
+    UInt16Type,
+    Int16Type,
+    UInt32Type,
+    Int32Type,
+    UInt64Type,
+    Int64Type,
+)
+_IndexT_co = TypeVar(
+    "_IndexT_co",
+    UInt8Type,
+    Int8Type,
+    UInt16Type,
+    Int16Type,
+    UInt32Type,
+    Int32Type,
+    UInt64Type,
+    Int64Type,
+    covariant=True,
+)
+_ValueT = TypeVar("_ValueT", bound=DataType, default=DataType)
+_ValueT_co = TypeVar("_ValueT_co", bound=DataType, default=DataType, 
covariant=True)
+_K = TypeVar("_K", bound=DataType, default=DataType)
+_K_co = TypeVar("_K_co", bound=DataType, default=DataType, covariant=True)
+_Ordered = TypeVar("_Ordered", Literal[True], Literal[False], 
default=Literal[False])
+_Ordered_co = TypeVar(
+    "_Ordered_co", Literal[True], Literal[False], default=Literal[False], 
covariant=True
+)
+
+class DictionaryType(DataType, Generic[_IndexT_co, _ValueT_co, _Ordered_co]):
+    @property
+    def ordered(self) -> _Ordered_co: ...
+    @property
+    def index_type(self) -> _IndexT_co: ...
+    @property
+    def value_type(self) -> _ValueT_co: ...
+
+class MapType(DataType, Generic[_K_co, _ValueT_co, _Ordered_co]):
+    @property
+    def key_field(self) -> Field[_K_co]: ...
+    @property
+    def key_type(self) -> _K_co: ...
+    @property
+    def item_field(self) -> Field[_ValueT_co]: ...
+    @property
+    def item_type(self) -> _ValueT_co: ...
+    @property
+    def keys_sorted(self) -> _Ordered_co: ...
+
+class StructType(DataType):
+    def get_field_index(self, name: str | bytes) -> int: ...
+    def field(self, i: int | str | bytes) -> Field[Any]: ...
+    def get_all_field_indices(self, name: str | bytes) -> list[int]: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+
+    __getitem__ = field
+    @property
+    def names(self) -> list[str]: ...
+    @property
+    def fields(self) -> list[Field[Any]]: ...
+
+class UnionType(DataType):
+    @property
+    def mode(self) -> Literal["sparse", "dense"]: ...
+    @property
+    def type_codes(self) -> list[int]: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+    def field(self, i: int) -> Field[Any]: ...
+
+    __getitem__ = field
+
+class SparseUnionType(UnionType):
+    @property
+    def mode(self) -> Literal["sparse"]: ...
+
+class DenseUnionType(UnionType):
+    @property
+    def mode(self) -> Literal["dense"]: ...
+
+_RunEndType = TypeVar("_RunEndType", Int16Type, Int32Type, Int64Type)
+_RunEndType_co = TypeVar(
+    "_RunEndType_co", Int16Type, Int32Type, Int64Type, covariant=True
+)
+
+class RunEndEncodedType(DataType, Generic[_RunEndType_co, _ValueT_co]):
+    @property
+    def run_end_type(self) -> _RunEndType_co: ...
+    @property
+    def value_type(self) -> _ValueT_co: ...
+
+_StorageT = TypeVar("_StorageT", bound=Array[Any] | ChunkedArray[Any])
+
+class BaseExtensionType(DataType):
+    def __arrow_ext_class__(self) -> type[ExtensionArray[Any]]: ...
+    def __arrow_ext_scalar_class__(self) -> type[ExtensionScalar]: ...
+    @property
+    def extension_name(self) -> str: ...
+    @property
+    def storage_type(self) -> DataType: ...
+    def wrap_array(self, storage: _StorageT) -> _StorageT: ...

Review Comment:
   `wrap_array()` produces an extension array rather than preserving the 
storage array's concrete type; chunked inputs likewise contain extension arrays 
in the result. Returning `_StorageT` therefore describes the wrong subclass and 
element type. Annotate array and chunked-array results separately instead of 
returning the input type variable.



##########
python/pyarrow-stubs/pyarrow/_types.pyi:
##########
@@ -0,0 +1,768 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import datetime as dt  # noqa: F401
+
+from collections.abc import Iterable, Iterator, Mapping, Sequence
+from decimal import Decimal  # noqa: F401
+from typing import Any, Generic, Literal, Protocol, TypeAlias, overload
+
+import numpy as np
+import pandas as pd  # type: ignore[import-not-found,import-untyped]  # 
pyright: ignore[reportMissingImports,reportMissingTypeStubs]  # noqa: E501
+from typing_extensions import Self, TypeVar, deprecated
+
+from pyarrow._stubs_typing import SupportsArrowSchema, TimeUnit
+from pyarrow.lib import (  # noqa: F401
+    Array,
+    Buffer,
+    ChunkedArray,
+    ExtensionArray,
+    ExtensionScalar,
+    MemoryPool,
+    MonthDayNano,
+    Table,
+)
+
+class _Weakrefable: ...
+class _Metadata(_Weakrefable): ...
+
+class DataType(_Weakrefable):
+    def field(self, i: int) -> Field[Any]: ...
+    @property
+    def id(self) -> int: ...
+    @property
+    def bit_width(self) -> int: ...
+    @property
+    def byte_width(self) -> int: ...
+    @property
+    def num_fields(self) -> int: ...
+    @property
+    def num_buffers(self) -> int: ...
+    @property
+    def has_variadic_buffers(self) -> bool: ...
+    def __hash__(self) -> int: ...
+    def equals(
+        self, other: DataType | str, *, check_metadata: bool = False
+    ) -> bool: ...
+    def to_pandas_dtype(self) -> _PandasDtype: ...
+    def _export_to_c(self, out_ptr: int) -> None: ...
+    @classmethod
+    def _import_from_c(cls, in_ptr: int) -> Self: ...
+    def __arrow_c_schema__(self) -> Any: ...
+    @classmethod
+    def _import_from_c_capsule(cls, schema: Any) -> Self: ...
+
+_AsPyType = TypeVar("_AsPyType")
+_DataTypeT = TypeVar("_DataTypeT", bound=DataType)
+_DataTypeT_co = TypeVar("_DataTypeT_co", bound=DataType, covariant=True)
+_Size = TypeVar("_Size", bound=int, default=int)
+_PandasDtype: TypeAlias = (
+    type[np.generic] | np.dtype[Any] | pd.api.extensions.ExtensionDtype
+)
+
+class _BasicDataType(DataType, Generic[_AsPyType]): ...
+class NullType(_BasicDataType[None]): ...
+class BoolType(_BasicDataType[bool]): ...
+class UInt8Type(_BasicDataType[int]): ...
+class Int8Type(_BasicDataType[int]): ...
+class UInt16Type(_BasicDataType[int]): ...
+class Int16Type(_BasicDataType[int]): ...
+class UInt32Type(_BasicDataType[int]): ...
+class Int32Type(_BasicDataType[int]): ...
+class UInt64Type(_BasicDataType[int]): ...
+class Int64Type(_BasicDataType[int]): ...
+class Float16Type(_BasicDataType[float]): ...
+class Float32Type(_BasicDataType[float]): ...
+class Float64Type(_BasicDataType[float]): ...
+class Date32Type(_BasicDataType[dt.date]): ...
+class Date64Type(_BasicDataType[dt.date]): ...
+class MonthDayNanoIntervalType(_BasicDataType[MonthDayNano]): ...
+class StringType(_BasicDataType[str]): ...
+class LargeStringType(_BasicDataType[str]): ...
+class StringViewType(_BasicDataType[str]): ...
+class BinaryType(_BasicDataType[bytes]): ...
+class LargeBinaryType(_BasicDataType[bytes]): ...
+class BinaryViewType(_BasicDataType[bytes]): ...
+
+_Unit = TypeVar("_Unit", bound=TimeUnit, default=Literal["us"])
+_Tz = TypeVar("_Tz", str, None, default=None)
+
+class TimestampType(_BasicDataType[dt.datetime], Generic[_Unit, _Tz]):
+    @property
+    def unit(self) -> _Unit: ...
+    @property
+    def tz(self) -> _Tz: ...
+
+_Time32Unit = TypeVar("_Time32Unit", bound=Literal["s", "ms"])
+
+class Time32Type(_BasicDataType[dt.time], Generic[_Time32Unit]):
+    @property
+    def unit(self) -> _Time32Unit: ...
+
+_Time64Unit = TypeVar("_Time64Unit", bound=Literal["us", "ns"])
+
+class Time64Type(_BasicDataType[dt.time], Generic[_Time64Unit]):
+    @property
+    def unit(self) -> _Time64Unit: ...
+
+class DurationType(_BasicDataType[dt.timedelta], Generic[_Unit]):
+    @property
+    def unit(self) -> _Unit: ...
+
+_FixedSizeBinaryAsPyType = TypeVar("_FixedSizeBinaryAsPyType", default=bytes)
+
+class FixedSizeBinaryType(_BasicDataType[_FixedSizeBinaryAsPyType]): ...
+
+_Precision = TypeVar("_Precision", bound=int, default=int)
+_Scale = TypeVar("_Scale", bound=int, default=int)
+_Precision_co = TypeVar("_Precision_co", bound=int, default=int, 
covariant=True)
+_Scale_co = TypeVar("_Scale_co", bound=int, default=int, covariant=True)
+
+class _HasPrecisionScale(Protocol[_Precision_co, _Scale_co]):
+    @property
+    def precision(self) -> _Precision_co: ...
+    @property
+    def scale(self) -> _Scale_co: ...
+
+class Decimal32Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal64Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal128Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal256Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+
+class ListType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class LargeListType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class ListViewType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class LargeListViewType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class FixedSizeListType(DataType, Generic[_DataTypeT_co, _Size]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+    @property
+    def list_size(self) -> _Size: ...
+
+class DictionaryMemo(_Weakrefable): ...
+
+_IndexT = TypeVar(
+    "_IndexT",
+    UInt8Type,
+    Int8Type,
+    UInt16Type,
+    Int16Type,
+    UInt32Type,
+    Int32Type,
+    UInt64Type,
+    Int64Type,
+)
+_IndexT_co = TypeVar(
+    "_IndexT_co",
+    UInt8Type,
+    Int8Type,
+    UInt16Type,
+    Int16Type,
+    UInt32Type,
+    Int32Type,
+    UInt64Type,
+    Int64Type,
+    covariant=True,
+)
+_ValueT = TypeVar("_ValueT", bound=DataType, default=DataType)
+_ValueT_co = TypeVar("_ValueT_co", bound=DataType, default=DataType, 
covariant=True)
+_K = TypeVar("_K", bound=DataType, default=DataType)
+_K_co = TypeVar("_K_co", bound=DataType, default=DataType, covariant=True)
+_Ordered = TypeVar("_Ordered", Literal[True], Literal[False], 
default=Literal[False])
+_Ordered_co = TypeVar(
+    "_Ordered_co", Literal[True], Literal[False], default=Literal[False], 
covariant=True
+)
+
+class DictionaryType(DataType, Generic[_IndexT_co, _ValueT_co, _Ordered_co]):
+    @property
+    def ordered(self) -> _Ordered_co: ...
+    @property
+    def index_type(self) -> _IndexT_co: ...
+    @property
+    def value_type(self) -> _ValueT_co: ...
+
+class MapType(DataType, Generic[_K_co, _ValueT_co, _Ordered_co]):
+    @property
+    def key_field(self) -> Field[_K_co]: ...
+    @property
+    def key_type(self) -> _K_co: ...
+    @property
+    def item_field(self) -> Field[_ValueT_co]: ...
+    @property
+    def item_type(self) -> _ValueT_co: ...
+    @property
+    def keys_sorted(self) -> _Ordered_co: ...
+
+class StructType(DataType):
+    def get_field_index(self, name: str | bytes) -> int: ...
+    def field(self, i: int | str | bytes) -> Field[Any]: ...
+    def get_all_field_indices(self, name: str | bytes) -> list[int]: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+
+    __getitem__ = field
+    @property
+    def names(self) -> list[str]: ...
+    @property
+    def fields(self) -> list[Field[Any]]: ...
+
+class UnionType(DataType):
+    @property
+    def mode(self) -> Literal["sparse", "dense"]: ...
+    @property
+    def type_codes(self) -> list[int]: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+    def field(self, i: int) -> Field[Any]: ...
+
+    __getitem__ = field
+
+class SparseUnionType(UnionType):
+    @property
+    def mode(self) -> Literal["sparse"]: ...
+
+class DenseUnionType(UnionType):
+    @property
+    def mode(self) -> Literal["dense"]: ...
+
+_RunEndType = TypeVar("_RunEndType", Int16Type, Int32Type, Int64Type)
+_RunEndType_co = TypeVar(
+    "_RunEndType_co", Int16Type, Int32Type, Int64Type, covariant=True
+)
+
+class RunEndEncodedType(DataType, Generic[_RunEndType_co, _ValueT_co]):
+    @property
+    def run_end_type(self) -> _RunEndType_co: ...
+    @property
+    def value_type(self) -> _ValueT_co: ...
+
+_StorageT = TypeVar("_StorageT", bound=Array[Any] | ChunkedArray[Any])
+
+class BaseExtensionType(DataType):
+    def __arrow_ext_class__(self) -> type[ExtensionArray[Any]]: ...
+    def __arrow_ext_scalar_class__(self) -> type[ExtensionScalar]: ...
+    @property
+    def extension_name(self) -> str: ...
+    @property
+    def storage_type(self) -> DataType: ...
+    def wrap_array(self, storage: _StorageT) -> _StorageT: ...
+
+class ExtensionType(BaseExtensionType):
+    def __init__(self, storage_type: DataType, extension_name: str) -> None: 
...
+    def __arrow_ext_serialize__(self) -> bytes: ...
+    @classmethod
+    def __arrow_ext_deserialize__(
+        cls, storage_type: DataType, serialized: bytes
+    ) -> Self: ...
+
+class FixedShapeTensorType(BaseExtensionType, Generic[_ValueT_co]):
+    @property
+    def value_type(self) -> _ValueT_co: ...
+    @property
+    def shape(self) -> list[int]: ...
+    @property
+    def dim_names(self) -> list[str] | None: ...
+    @property
+    def permutation(self) -> list[int] | None: ...
+
+class Bool8Type(BaseExtensionType): ...
+class UuidType(BaseExtensionType): ...
+class JsonType(BaseExtensionType): ...
+
+class OpaqueType(BaseExtensionType):
+    @property
+    def type_name(self) -> str: ...
+    @property
+    def vendor_name(self) -> str: ...
+
+class UnknownExtensionType(ExtensionType):
+    def __init__(self, storage_type: DataType, serialized: bytes) -> None: ...
+
+def register_extension_type(ext_type: BaseExtensionType) -> None: ...
+def unregister_extension_type(type_name: str) -> None: ...
+
+_FieldName: TypeAlias = str | bytes
+_StrOrBytes: TypeAlias = str | bytes
+_MetadataMapping: TypeAlias = (
+    Mapping[bytes, bytes]
+    | Mapping[str, str]
+    | Mapping[bytes, str]
+    | Mapping[str, bytes]
+)
+_MetadataIterable: TypeAlias = Iterable[tuple[_StrOrBytes, _StrOrBytes]]
+_KeyValueMetadataInput: TypeAlias = _MetadataMapping | _MetadataIterable | None
+_DataTypeAlias: TypeAlias = Literal[
+    "null",
+    "bool",
+    "boolean",
+    "i1",
+    "int8",
+    "i2",
+    "int16",
+    "i4",
+    "int32",
+    "i8",
+    "int64",
+    "u1",
+    "uint8",
+    "u2",
+    "uint16",
+    "u4",
+    "uint32",
+    "u8",
+    "uint64",
+    "f2",
+    "halffloat",
+    "float16",
+    "f4",
+    "float",
+    "float32",
+    "f8",
+    "double",
+    "float64",
+    "string",
+    "str",
+    "utf8",
+    "binary",
+    "large_string",
+    "large_str",
+    "large_utf8",
+    "large_binary",
+    "binary_view",
+    "string_view",
+    "date32",
+    "date64",
+    "date32[day]",
+    "date64[ms]",
+    "time32[s]",
+    "time32[ms]",
+    "time64[us]",
+    "time64[ns]",
+    "timestamp[s]",
+    "timestamp[ms]",
+    "timestamp[us]",
+    "timestamp[ns]",
+    "duration[s]",
+    "duration[ms]",
+    "duration[us]",
+    "duration[ns]",
+    "month_day_nano_interval",
+]
+_DataTypeAliasInput: TypeAlias = _DataTypeAlias | str
+_DataTypeLike: TypeAlias = DataType | _DataTypeAliasInput
+
+class KeyValueMetadata(_Metadata, Mapping[bytes, bytes]):
+    def __init__(
+        self,
+        __arg0__: _KeyValueMetadataInput | KeyValueMetadata = None,
+        **kwargs: str | bytes,
+    ) -> None: ...
+    def equals(self, other: KeyValueMetadata) -> bool: ...
+    def __len__(self) -> int: ...
+    def __contains__(self, /, __key: object) -> bool: ...
+    def __getitem__(self, /, __key: str | bytes) -> bytes: ...
+    def __iter__(self) -> Iterator[bytes]: ...
+    def key(self, i: int) -> bytes: ...
+    def value(self, i: int) -> bytes: ...
+    def get_all(self, key: str | bytes) -> list[bytes]: ...
+    def to_dict(self) -> dict[bytes, bytes]: ...
+
+class Field(_Weakrefable, Generic[_DataTypeT_co]):
+    def equals(self, other: Field[Any], check_metadata: bool = False) -> bool: 
...
+    def __hash__(self) -> int: ...
+    @property
+    def nullable(self) -> bool: ...
+    @property
+    def name(self) -> str: ...
+    @property
+    def metadata(self) -> dict[bytes, bytes] | None: ...
+    @property
+    def type(self) -> _DataTypeT_co: ...
+    def with_metadata(
+        self,
+        metadata: _KeyValueMetadataInput | KeyValueMetadata,
+    ) -> Self: ...
+    def remove_metadata(self) -> Self: ...
+    def with_type(self, new_type: _DataTypeT) -> Field[_DataTypeT]: ...
+    def with_name(self, name: str | bytes) -> Self: ...
+    def with_nullable(self, nullable: bool) -> Field[_DataTypeT_co]: ...
+    def flatten(self) -> list[Field[Any]]: ...
+    def _export_to_c(self, out_ptr: int) -> None: ...
+    @classmethod
+    def _import_from_c(cls, in_ptr: int) -> Self: ...
+    def __arrow_c_schema__(self) -> Any: ...
+    @classmethod
+    def _import_from_c_capsule(cls, schema: Any) -> Self: ...
+
+_FieldTuple: TypeAlias = (
+    tuple[_FieldName, _DataTypeLike]
+    | tuple[_FieldName, _DataTypeLike, bool | None]
+    | tuple[
+        _FieldName,
+        _DataTypeLike,
+        bool | None,
+        _KeyValueMetadataInput | KeyValueMetadata,
+    ]
+)
+_StructFieldsInput: TypeAlias = (
+    Iterable[Field[Any] | _FieldTuple] | Mapping[_FieldName, _DataTypeLike]

Review Comment:
   Mapping is invariant in its key type, so this rejects existing `dict[str, 
DataType]` and `OrderedDict[str, DataType]` variables: neither is a Mapping[str 
| bytes, ...]. Both schema() and struct() consume this alias, so supported 
mapping-based construction fails type checking. Please add str-keyed and 
bytes-keyed mapping alternatives while retaining mixed-key support.



##########
python/pyarrow-stubs/pyarrow/_types.pyi:
##########
@@ -0,0 +1,768 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import datetime as dt  # noqa: F401
+
+from collections.abc import Iterable, Iterator, Mapping, Sequence
+from decimal import Decimal  # noqa: F401
+from typing import Any, Generic, Literal, Protocol, TypeAlias, overload
+
+import numpy as np
+import pandas as pd  # type: ignore[import-not-found,import-untyped]  # 
pyright: ignore[reportMissingImports,reportMissingTypeStubs]  # noqa: E501
+from typing_extensions import Self, TypeVar, deprecated
+
+from pyarrow._stubs_typing import SupportsArrowSchema, TimeUnit
+from pyarrow.lib import (  # noqa: F401
+    Array,
+    Buffer,
+    ChunkedArray,
+    ExtensionArray,
+    ExtensionScalar,
+    MemoryPool,
+    MonthDayNano,
+    Table,
+)
+
+class _Weakrefable: ...
+class _Metadata(_Weakrefable): ...
+
+class DataType(_Weakrefable):
+    def field(self, i: int) -> Field[Any]: ...
+    @property
+    def id(self) -> int: ...
+    @property
+    def bit_width(self) -> int: ...
+    @property
+    def byte_width(self) -> int: ...
+    @property
+    def num_fields(self) -> int: ...
+    @property
+    def num_buffers(self) -> int: ...
+    @property
+    def has_variadic_buffers(self) -> bool: ...
+    def __hash__(self) -> int: ...
+    def equals(
+        self, other: DataType | str, *, check_metadata: bool = False
+    ) -> bool: ...
+    def to_pandas_dtype(self) -> _PandasDtype: ...
+    def _export_to_c(self, out_ptr: int) -> None: ...
+    @classmethod
+    def _import_from_c(cls, in_ptr: int) -> Self: ...
+    def __arrow_c_schema__(self) -> Any: ...
+    @classmethod
+    def _import_from_c_capsule(cls, schema: Any) -> Self: ...
+
+_AsPyType = TypeVar("_AsPyType")
+_DataTypeT = TypeVar("_DataTypeT", bound=DataType)
+_DataTypeT_co = TypeVar("_DataTypeT_co", bound=DataType, covariant=True)
+_Size = TypeVar("_Size", bound=int, default=int)
+_PandasDtype: TypeAlias = (
+    type[np.generic] | np.dtype[Any] | pd.api.extensions.ExtensionDtype
+)
+
+class _BasicDataType(DataType, Generic[_AsPyType]): ...
+class NullType(_BasicDataType[None]): ...
+class BoolType(_BasicDataType[bool]): ...
+class UInt8Type(_BasicDataType[int]): ...
+class Int8Type(_BasicDataType[int]): ...
+class UInt16Type(_BasicDataType[int]): ...
+class Int16Type(_BasicDataType[int]): ...
+class UInt32Type(_BasicDataType[int]): ...
+class Int32Type(_BasicDataType[int]): ...
+class UInt64Type(_BasicDataType[int]): ...
+class Int64Type(_BasicDataType[int]): ...
+class Float16Type(_BasicDataType[float]): ...
+class Float32Type(_BasicDataType[float]): ...
+class Float64Type(_BasicDataType[float]): ...
+class Date32Type(_BasicDataType[dt.date]): ...
+class Date64Type(_BasicDataType[dt.date]): ...
+class MonthDayNanoIntervalType(_BasicDataType[MonthDayNano]): ...
+class StringType(_BasicDataType[str]): ...
+class LargeStringType(_BasicDataType[str]): ...
+class StringViewType(_BasicDataType[str]): ...
+class BinaryType(_BasicDataType[bytes]): ...
+class LargeBinaryType(_BasicDataType[bytes]): ...
+class BinaryViewType(_BasicDataType[bytes]): ...
+
+_Unit = TypeVar("_Unit", bound=TimeUnit, default=Literal["us"])
+_Tz = TypeVar("_Tz", str, None, default=None)
+
+class TimestampType(_BasicDataType[dt.datetime], Generic[_Unit, _Tz]):
+    @property
+    def unit(self) -> _Unit: ...
+    @property
+    def tz(self) -> _Tz: ...
+
+_Time32Unit = TypeVar("_Time32Unit", bound=Literal["s", "ms"])
+
+class Time32Type(_BasicDataType[dt.time], Generic[_Time32Unit]):
+    @property
+    def unit(self) -> _Time32Unit: ...
+
+_Time64Unit = TypeVar("_Time64Unit", bound=Literal["us", "ns"])
+
+class Time64Type(_BasicDataType[dt.time], Generic[_Time64Unit]):
+    @property
+    def unit(self) -> _Time64Unit: ...
+
+class DurationType(_BasicDataType[dt.timedelta], Generic[_Unit]):
+    @property
+    def unit(self) -> _Unit: ...
+
+_FixedSizeBinaryAsPyType = TypeVar("_FixedSizeBinaryAsPyType", default=bytes)
+
+class FixedSizeBinaryType(_BasicDataType[_FixedSizeBinaryAsPyType]): ...
+
+_Precision = TypeVar("_Precision", bound=int, default=int)
+_Scale = TypeVar("_Scale", bound=int, default=int)
+_Precision_co = TypeVar("_Precision_co", bound=int, default=int, 
covariant=True)
+_Scale_co = TypeVar("_Scale_co", bound=int, default=int, covariant=True)
+
+class _HasPrecisionScale(Protocol[_Precision_co, _Scale_co]):
+    @property
+    def precision(self) -> _Precision_co: ...
+    @property
+    def scale(self) -> _Scale_co: ...
+
+class Decimal32Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal64Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal128Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal256Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+
+class ListType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class LargeListType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class ListViewType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class LargeListViewType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class FixedSizeListType(DataType, Generic[_DataTypeT_co, _Size]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+    @property
+    def list_size(self) -> _Size: ...
+
+class DictionaryMemo(_Weakrefable): ...
+
+_IndexT = TypeVar(
+    "_IndexT",
+    UInt8Type,
+    Int8Type,
+    UInt16Type,
+    Int16Type,
+    UInt32Type,
+    Int32Type,
+    UInt64Type,
+    Int64Type,
+)
+_IndexT_co = TypeVar(
+    "_IndexT_co",
+    UInt8Type,
+    Int8Type,
+    UInt16Type,
+    Int16Type,
+    UInt32Type,
+    Int32Type,
+    UInt64Type,
+    Int64Type,
+    covariant=True,
+)
+_ValueT = TypeVar("_ValueT", bound=DataType, default=DataType)
+_ValueT_co = TypeVar("_ValueT_co", bound=DataType, default=DataType, 
covariant=True)
+_K = TypeVar("_K", bound=DataType, default=DataType)
+_K_co = TypeVar("_K_co", bound=DataType, default=DataType, covariant=True)
+_Ordered = TypeVar("_Ordered", Literal[True], Literal[False], 
default=Literal[False])
+_Ordered_co = TypeVar(
+    "_Ordered_co", Literal[True], Literal[False], default=Literal[False], 
covariant=True
+)
+
+class DictionaryType(DataType, Generic[_IndexT_co, _ValueT_co, _Ordered_co]):
+    @property
+    def ordered(self) -> _Ordered_co: ...
+    @property
+    def index_type(self) -> _IndexT_co: ...
+    @property
+    def value_type(self) -> _ValueT_co: ...
+
+class MapType(DataType, Generic[_K_co, _ValueT_co, _Ordered_co]):
+    @property
+    def key_field(self) -> Field[_K_co]: ...
+    @property
+    def key_type(self) -> _K_co: ...
+    @property
+    def item_field(self) -> Field[_ValueT_co]: ...
+    @property
+    def item_type(self) -> _ValueT_co: ...
+    @property
+    def keys_sorted(self) -> _Ordered_co: ...
+
+class StructType(DataType):
+    def get_field_index(self, name: str | bytes) -> int: ...
+    def field(self, i: int | str | bytes) -> Field[Any]: ...
+    def get_all_field_indices(self, name: str | bytes) -> list[int]: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+
+    __getitem__ = field
+    @property
+    def names(self) -> list[str]: ...
+    @property
+    def fields(self) -> list[Field[Any]]: ...
+
+class UnionType(DataType):
+    @property
+    def mode(self) -> Literal["sparse", "dense"]: ...
+    @property
+    def type_codes(self) -> list[int]: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+    def field(self, i: int) -> Field[Any]: ...
+
+    __getitem__ = field
+
+class SparseUnionType(UnionType):
+    @property
+    def mode(self) -> Literal["sparse"]: ...
+
+class DenseUnionType(UnionType):
+    @property
+    def mode(self) -> Literal["dense"]: ...
+
+_RunEndType = TypeVar("_RunEndType", Int16Type, Int32Type, Int64Type)
+_RunEndType_co = TypeVar(
+    "_RunEndType_co", Int16Type, Int32Type, Int64Type, covariant=True
+)
+
+class RunEndEncodedType(DataType, Generic[_RunEndType_co, _ValueT_co]):
+    @property
+    def run_end_type(self) -> _RunEndType_co: ...
+    @property
+    def value_type(self) -> _ValueT_co: ...
+
+_StorageT = TypeVar("_StorageT", bound=Array[Any] | ChunkedArray[Any])
+
+class BaseExtensionType(DataType):
+    def __arrow_ext_class__(self) -> type[ExtensionArray[Any]]: ...
+    def __arrow_ext_scalar_class__(self) -> type[ExtensionScalar]: ...
+    @property
+    def extension_name(self) -> str: ...
+    @property
+    def storage_type(self) -> DataType: ...
+    def wrap_array(self, storage: _StorageT) -> _StorageT: ...
+
+class ExtensionType(BaseExtensionType):
+    def __init__(self, storage_type: DataType, extension_name: str) -> None: 
...
+    def __arrow_ext_serialize__(self) -> bytes: ...
+    @classmethod
+    def __arrow_ext_deserialize__(
+        cls, storage_type: DataType, serialized: bytes
+    ) -> Self: ...
+
+class FixedShapeTensorType(BaseExtensionType, Generic[_ValueT_co]):
+    @property
+    def value_type(self) -> _ValueT_co: ...
+    @property
+    def shape(self) -> list[int]: ...
+    @property
+    def dim_names(self) -> list[str] | None: ...
+    @property
+    def permutation(self) -> list[int] | None: ...
+
+class Bool8Type(BaseExtensionType): ...
+class UuidType(BaseExtensionType): ...
+class JsonType(BaseExtensionType): ...
+
+class OpaqueType(BaseExtensionType):
+    @property
+    def type_name(self) -> str: ...
+    @property
+    def vendor_name(self) -> str: ...
+
+class UnknownExtensionType(ExtensionType):
+    def __init__(self, storage_type: DataType, serialized: bytes) -> None: ...
+
+def register_extension_type(ext_type: BaseExtensionType) -> None: ...
+def unregister_extension_type(type_name: str) -> None: ...
+
+_FieldName: TypeAlias = str | bytes
+_StrOrBytes: TypeAlias = str | bytes
+_MetadataMapping: TypeAlias = (
+    Mapping[bytes, bytes]
+    | Mapping[str, str]
+    | Mapping[bytes, str]
+    | Mapping[str, bytes]
+)
+_MetadataIterable: TypeAlias = Iterable[tuple[_StrOrBytes, _StrOrBytes]]
+_KeyValueMetadataInput: TypeAlias = _MetadataMapping | _MetadataIterable | None
+_DataTypeAlias: TypeAlias = Literal[
+    "null",
+    "bool",
+    "boolean",
+    "i1",
+    "int8",
+    "i2",
+    "int16",
+    "i4",
+    "int32",
+    "i8",
+    "int64",
+    "u1",
+    "uint8",
+    "u2",
+    "uint16",
+    "u4",
+    "uint32",
+    "u8",
+    "uint64",
+    "f2",
+    "halffloat",
+    "float16",
+    "f4",
+    "float",
+    "float32",
+    "f8",
+    "double",
+    "float64",
+    "string",
+    "str",
+    "utf8",
+    "binary",
+    "large_string",
+    "large_str",
+    "large_utf8",
+    "large_binary",
+    "binary_view",
+    "string_view",
+    "date32",
+    "date64",
+    "date32[day]",
+    "date64[ms]",
+    "time32[s]",
+    "time32[ms]",
+    "time64[us]",
+    "time64[ns]",
+    "timestamp[s]",
+    "timestamp[ms]",
+    "timestamp[us]",
+    "timestamp[ns]",
+    "duration[s]",
+    "duration[ms]",
+    "duration[us]",
+    "duration[ns]",
+    "month_day_nano_interval",
+]
+_DataTypeAliasInput: TypeAlias = _DataTypeAlias | str
+_DataTypeLike: TypeAlias = DataType | _DataTypeAliasInput
+
+class KeyValueMetadata(_Metadata, Mapping[bytes, bytes]):

Review Comment:
   Inheriting `Mapping` without declaring `keys()`, `values()` and `items()` 
gives them view-returning types, but the runtime returns iterators 
(`test_types.py:1173–1175`). This rejects valid `next(metadata.items())` calls 
while accepting `len(metadata.keys())`, which raises `TypeError`. Explicitly 
model these iterator-returning methods and reconcile their signatures with the 
`Mapping` base.



##########
python/pyarrow-stubs/pyarrow/_types.pyi:
##########
@@ -0,0 +1,768 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import datetime as dt  # noqa: F401
+
+from collections.abc import Iterable, Iterator, Mapping, Sequence
+from decimal import Decimal  # noqa: F401
+from typing import Any, Generic, Literal, Protocol, TypeAlias, overload
+
+import numpy as np
+import pandas as pd  # type: ignore[import-not-found,import-untyped]  # 
pyright: ignore[reportMissingImports,reportMissingTypeStubs]  # noqa: E501
+from typing_extensions import Self, TypeVar, deprecated
+
+from pyarrow._stubs_typing import SupportsArrowSchema, TimeUnit
+from pyarrow.lib import (  # noqa: F401
+    Array,
+    Buffer,
+    ChunkedArray,
+    ExtensionArray,
+    ExtensionScalar,
+    MemoryPool,
+    MonthDayNano,
+    Table,
+)
+
+class _Weakrefable: ...
+class _Metadata(_Weakrefable): ...
+
+class DataType(_Weakrefable):
+    def field(self, i: int) -> Field[Any]: ...
+    @property
+    def id(self) -> int: ...
+    @property
+    def bit_width(self) -> int: ...
+    @property
+    def byte_width(self) -> int: ...
+    @property
+    def num_fields(self) -> int: ...
+    @property
+    def num_buffers(self) -> int: ...
+    @property
+    def has_variadic_buffers(self) -> bool: ...
+    def __hash__(self) -> int: ...
+    def equals(
+        self, other: DataType | str, *, check_metadata: bool = False
+    ) -> bool: ...
+    def to_pandas_dtype(self) -> _PandasDtype: ...
+    def _export_to_c(self, out_ptr: int) -> None: ...
+    @classmethod
+    def _import_from_c(cls, in_ptr: int) -> Self: ...
+    def __arrow_c_schema__(self) -> Any: ...
+    @classmethod
+    def _import_from_c_capsule(cls, schema: Any) -> Self: ...
+
+_AsPyType = TypeVar("_AsPyType")
+_DataTypeT = TypeVar("_DataTypeT", bound=DataType)
+_DataTypeT_co = TypeVar("_DataTypeT_co", bound=DataType, covariant=True)
+_Size = TypeVar("_Size", bound=int, default=int)
+_PandasDtype: TypeAlias = (
+    type[np.generic] | np.dtype[Any] | pd.api.extensions.ExtensionDtype
+)
+
+class _BasicDataType(DataType, Generic[_AsPyType]): ...
+class NullType(_BasicDataType[None]): ...
+class BoolType(_BasicDataType[bool]): ...
+class UInt8Type(_BasicDataType[int]): ...
+class Int8Type(_BasicDataType[int]): ...
+class UInt16Type(_BasicDataType[int]): ...
+class Int16Type(_BasicDataType[int]): ...
+class UInt32Type(_BasicDataType[int]): ...
+class Int32Type(_BasicDataType[int]): ...
+class UInt64Type(_BasicDataType[int]): ...
+class Int64Type(_BasicDataType[int]): ...
+class Float16Type(_BasicDataType[float]): ...
+class Float32Type(_BasicDataType[float]): ...
+class Float64Type(_BasicDataType[float]): ...
+class Date32Type(_BasicDataType[dt.date]): ...
+class Date64Type(_BasicDataType[dt.date]): ...
+class MonthDayNanoIntervalType(_BasicDataType[MonthDayNano]): ...
+class StringType(_BasicDataType[str]): ...
+class LargeStringType(_BasicDataType[str]): ...
+class StringViewType(_BasicDataType[str]): ...
+class BinaryType(_BasicDataType[bytes]): ...
+class LargeBinaryType(_BasicDataType[bytes]): ...
+class BinaryViewType(_BasicDataType[bytes]): ...
+
+_Unit = TypeVar("_Unit", bound=TimeUnit, default=Literal["us"])
+_Tz = TypeVar("_Tz", str, None, default=None)
+
+class TimestampType(_BasicDataType[dt.datetime], Generic[_Unit, _Tz]):
+    @property
+    def unit(self) -> _Unit: ...
+    @property
+    def tz(self) -> _Tz: ...
+
+_Time32Unit = TypeVar("_Time32Unit", bound=Literal["s", "ms"])
+
+class Time32Type(_BasicDataType[dt.time], Generic[_Time32Unit]):
+    @property
+    def unit(self) -> _Time32Unit: ...
+
+_Time64Unit = TypeVar("_Time64Unit", bound=Literal["us", "ns"])
+
+class Time64Type(_BasicDataType[dt.time], Generic[_Time64Unit]):
+    @property
+    def unit(self) -> _Time64Unit: ...
+
+class DurationType(_BasicDataType[dt.timedelta], Generic[_Unit]):
+    @property
+    def unit(self) -> _Unit: ...
+
+_FixedSizeBinaryAsPyType = TypeVar("_FixedSizeBinaryAsPyType", default=bytes)
+
+class FixedSizeBinaryType(_BasicDataType[_FixedSizeBinaryAsPyType]): ...
+
+_Precision = TypeVar("_Precision", bound=int, default=int)
+_Scale = TypeVar("_Scale", bound=int, default=int)
+_Precision_co = TypeVar("_Precision_co", bound=int, default=int, 
covariant=True)
+_Scale_co = TypeVar("_Scale_co", bound=int, default=int, covariant=True)
+
+class _HasPrecisionScale(Protocol[_Precision_co, _Scale_co]):
+    @property
+    def precision(self) -> _Precision_co: ...
+    @property
+    def scale(self) -> _Scale_co: ...
+
+class Decimal32Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal64Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal128Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+class Decimal256Type(
+    FixedSizeBinaryType[Decimal], _HasPrecisionScale[_Precision_co, _Scale_co]
+): ...
+
+class ListType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class LargeListType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class ListViewType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class LargeListViewType(DataType, Generic[_DataTypeT_co]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+
+class FixedSizeListType(DataType, Generic[_DataTypeT_co, _Size]):
+    @property
+    def value_field(self) -> Field[_DataTypeT_co]: ...
+    @property
+    def value_type(self) -> _DataTypeT_co: ...
+    @property
+    def list_size(self) -> _Size: ...
+
+class DictionaryMemo(_Weakrefable): ...
+
+_IndexT = TypeVar(
+    "_IndexT",
+    UInt8Type,
+    Int8Type,
+    UInt16Type,
+    Int16Type,
+    UInt32Type,
+    Int32Type,
+    UInt64Type,
+    Int64Type,
+)
+_IndexT_co = TypeVar(
+    "_IndexT_co",
+    UInt8Type,
+    Int8Type,
+    UInt16Type,
+    Int16Type,
+    UInt32Type,
+    Int32Type,
+    UInt64Type,
+    Int64Type,
+    covariant=True,
+)
+_ValueT = TypeVar("_ValueT", bound=DataType, default=DataType)
+_ValueT_co = TypeVar("_ValueT_co", bound=DataType, default=DataType, 
covariant=True)
+_K = TypeVar("_K", bound=DataType, default=DataType)
+_K_co = TypeVar("_K_co", bound=DataType, default=DataType, covariant=True)
+_Ordered = TypeVar("_Ordered", Literal[True], Literal[False], 
default=Literal[False])
+_Ordered_co = TypeVar(
+    "_Ordered_co", Literal[True], Literal[False], default=Literal[False], 
covariant=True
+)
+
+class DictionaryType(DataType, Generic[_IndexT_co, _ValueT_co, _Ordered_co]):
+    @property
+    def ordered(self) -> _Ordered_co: ...
+    @property
+    def index_type(self) -> _IndexT_co: ...
+    @property
+    def value_type(self) -> _ValueT_co: ...
+
+class MapType(DataType, Generic[_K_co, _ValueT_co, _Ordered_co]):
+    @property
+    def key_field(self) -> Field[_K_co]: ...
+    @property
+    def key_type(self) -> _K_co: ...
+    @property
+    def item_field(self) -> Field[_ValueT_co]: ...
+    @property
+    def item_type(self) -> _ValueT_co: ...
+    @property
+    def keys_sorted(self) -> _Ordered_co: ...
+
+class StructType(DataType):
+    def get_field_index(self, name: str | bytes) -> int: ...
+    def field(self, i: int | str | bytes) -> Field[Any]: ...
+    def get_all_field_indices(self, name: str | bytes) -> list[int]: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+
+    __getitem__ = field
+    @property
+    def names(self) -> list[str]: ...
+    @property
+    def fields(self) -> list[Field[Any]]: ...
+
+class UnionType(DataType):
+    @property
+    def mode(self) -> Literal["sparse", "dense"]: ...
+    @property
+    def type_codes(self) -> list[int]: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+    def field(self, i: int) -> Field[Any]: ...
+
+    __getitem__ = field
+
+class SparseUnionType(UnionType):
+    @property
+    def mode(self) -> Literal["sparse"]: ...
+
+class DenseUnionType(UnionType):
+    @property
+    def mode(self) -> Literal["dense"]: ...
+
+_RunEndType = TypeVar("_RunEndType", Int16Type, Int32Type, Int64Type)
+_RunEndType_co = TypeVar(
+    "_RunEndType_co", Int16Type, Int32Type, Int64Type, covariant=True
+)
+
+class RunEndEncodedType(DataType, Generic[_RunEndType_co, _ValueT_co]):
+    @property
+    def run_end_type(self) -> _RunEndType_co: ...
+    @property
+    def value_type(self) -> _ValueT_co: ...
+
+_StorageT = TypeVar("_StorageT", bound=Array[Any] | ChunkedArray[Any])
+
+class BaseExtensionType(DataType):
+    def __arrow_ext_class__(self) -> type[ExtensionArray[Any]]: ...
+    def __arrow_ext_scalar_class__(self) -> type[ExtensionScalar]: ...
+    @property
+    def extension_name(self) -> str: ...
+    @property
+    def storage_type(self) -> DataType: ...
+    def wrap_array(self, storage: _StorageT) -> _StorageT: ...
+
+class ExtensionType(BaseExtensionType):
+    def __init__(self, storage_type: DataType, extension_name: str) -> None: 
...
+    def __arrow_ext_serialize__(self) -> bytes: ...
+    @classmethod
+    def __arrow_ext_deserialize__(
+        cls, storage_type: DataType, serialized: bytes
+    ) -> Self: ...
+
+class FixedShapeTensorType(BaseExtensionType, Generic[_ValueT_co]):
+    @property
+    def value_type(self) -> _ValueT_co: ...
+    @property
+    def shape(self) -> list[int]: ...
+    @property
+    def dim_names(self) -> list[str] | None: ...
+    @property
+    def permutation(self) -> list[int] | None: ...
+
+class Bool8Type(BaseExtensionType): ...
+class UuidType(BaseExtensionType): ...
+class JsonType(BaseExtensionType): ...
+
+class OpaqueType(BaseExtensionType):
+    @property
+    def type_name(self) -> str: ...
+    @property
+    def vendor_name(self) -> str: ...
+
+class UnknownExtensionType(ExtensionType):
+    def __init__(self, storage_type: DataType, serialized: bytes) -> None: ...
+
+def register_extension_type(ext_type: BaseExtensionType) -> None: ...
+def unregister_extension_type(type_name: str) -> None: ...
+
+_FieldName: TypeAlias = str | bytes
+_StrOrBytes: TypeAlias = str | bytes
+_MetadataMapping: TypeAlias = (
+    Mapping[bytes, bytes]
+    | Mapping[str, str]
+    | Mapping[bytes, str]
+    | Mapping[str, bytes]
+)
+_MetadataIterable: TypeAlias = Iterable[tuple[_StrOrBytes, _StrOrBytes]]
+_KeyValueMetadataInput: TypeAlias = _MetadataMapping | _MetadataIterable | None
+_DataTypeAlias: TypeAlias = Literal[
+    "null",
+    "bool",
+    "boolean",
+    "i1",
+    "int8",
+    "i2",
+    "int16",
+    "i4",
+    "int32",
+    "i8",
+    "int64",
+    "u1",
+    "uint8",
+    "u2",
+    "uint16",
+    "u4",
+    "uint32",
+    "u8",
+    "uint64",
+    "f2",
+    "halffloat",
+    "float16",
+    "f4",
+    "float",
+    "float32",
+    "f8",
+    "double",
+    "float64",
+    "string",
+    "str",
+    "utf8",
+    "binary",
+    "large_string",
+    "large_str",
+    "large_utf8",
+    "large_binary",
+    "binary_view",
+    "string_view",
+    "date32",
+    "date64",
+    "date32[day]",
+    "date64[ms]",
+    "time32[s]",
+    "time32[ms]",
+    "time64[us]",
+    "time64[ns]",
+    "timestamp[s]",
+    "timestamp[ms]",
+    "timestamp[us]",
+    "timestamp[ns]",
+    "duration[s]",
+    "duration[ms]",
+    "duration[us]",
+    "duration[ns]",
+    "month_day_nano_interval",
+]
+_DataTypeAliasInput: TypeAlias = _DataTypeAlias | str
+_DataTypeLike: TypeAlias = DataType | _DataTypeAliasInput
+
+class KeyValueMetadata(_Metadata, Mapping[bytes, bytes]):
+    def __init__(
+        self,
+        __arg0__: _KeyValueMetadataInput | KeyValueMetadata = None,
+        **kwargs: str | bytes,
+    ) -> None: ...
+    def equals(self, other: KeyValueMetadata) -> bool: ...
+    def __len__(self) -> int: ...
+    def __contains__(self, /, __key: object) -> bool: ...
+    def __getitem__(self, /, __key: str | bytes) -> bytes: ...
+    def __iter__(self) -> Iterator[bytes]: ...
+    def key(self, i: int) -> bytes: ...
+    def value(self, i: int) -> bytes: ...
+    def get_all(self, key: str | bytes) -> list[bytes]: ...
+    def to_dict(self) -> dict[bytes, bytes]: ...
+
+class Field(_Weakrefable, Generic[_DataTypeT_co]):
+    def equals(self, other: Field[Any], check_metadata: bool = False) -> bool: 
...
+    def __hash__(self) -> int: ...
+    @property
+    def nullable(self) -> bool: ...
+    @property
+    def name(self) -> str: ...
+    @property
+    def metadata(self) -> dict[bytes, bytes] | None: ...
+    @property
+    def type(self) -> _DataTypeT_co: ...
+    def with_metadata(
+        self,
+        metadata: _KeyValueMetadataInput | KeyValueMetadata,
+    ) -> Self: ...
+    def remove_metadata(self) -> Self: ...
+    def with_type(self, new_type: _DataTypeT) -> Field[_DataTypeT]: ...
+    def with_name(self, name: str | bytes) -> Self: ...
+    def with_nullable(self, nullable: bool) -> Field[_DataTypeT_co]: ...
+    def flatten(self) -> list[Field[Any]]: ...
+    def _export_to_c(self, out_ptr: int) -> None: ...
+    @classmethod
+    def _import_from_c(cls, in_ptr: int) -> Self: ...
+    def __arrow_c_schema__(self) -> Any: ...
+    @classmethod
+    def _import_from_c_capsule(cls, schema: Any) -> Self: ...
+
+_FieldTuple: TypeAlias = (
+    tuple[_FieldName, _DataTypeLike]
+    | tuple[_FieldName, _DataTypeLike, bool | None]
+    | tuple[
+        _FieldName,
+        _DataTypeLike,
+        bool | None,
+        _KeyValueMetadataInput | KeyValueMetadata,
+    ]
+)
+_StructFieldsInput: TypeAlias = (
+    Iterable[Field[Any] | _FieldTuple] | Mapping[_FieldName, _DataTypeLike]
+)
+_SchemaFieldsInput: TypeAlias = _StructFieldsInput
+
+class Schema(_Weakrefable):
+    def __len__(self) -> int: ...
+    def __getitem__(self, key: int) -> Field[Any]: ...
+
+    _field = __getitem__
+    def __iter__(self) -> Iterator[Field[Any]]: ...
+    def __hash__(self) -> int: ...
+    def __sizeof__(self) -> int: ...
+    @property
+    def pandas_metadata(self) -> dict[str, Any] | None: ...
+    @property
+    def names(self) -> list[str]: ...
+    @property
+    def types(self) -> list[DataType]: ...
+    @property
+    def metadata(self) -> dict[bytes, bytes] | None: ...
+    def empty_table(self) -> Table: ...
+    def equals(self, other: Schema, check_metadata: bool = False) -> bool: ...
+    @classmethod
+    def from_pandas(
+        cls, df: pd.DataFrame, preserve_index: bool | None = None
+    ) -> Schema: ...
+    def field(self, i: int | str | bytes) -> Field[Any]: ...
+    @deprecated("Use 'field' instead")
+    def field_by_name(self, name: str | bytes) -> Field[Any] | None: ...
+    def get_field_index(self, name: str | bytes) -> int: ...
+    def get_all_field_indices(self, name: str | bytes) -> list[int]: ...
+    def append(self, field: Field[Any]) -> Schema: ...
+    def insert(self, i: int, field: Field[Any]) -> Schema: ...
+    def remove(self, i: int) -> Schema: ...
+    def set(self, i: int, field: Field[Any]) -> Schema: ...
+    @deprecated("Use 'with_metadata' instead")
+    def add_metadata(
+        self, metadata: _KeyValueMetadataInput | KeyValueMetadata
+    ) -> Schema: ...
+    def with_metadata(
+        self, metadata: _KeyValueMetadataInput | KeyValueMetadata
+    ) -> Schema: ...
+    def serialize(self, memory_pool: MemoryPool | None = None) -> Buffer: ...
+    def remove_metadata(self) -> Schema: ...
+    def to_string(
+        self,
+        truncate_metadata: bool = True,
+        show_field_metadata: bool = True,
+        show_schema_metadata: bool = True,
+        element_size_limit: int = 100,
+    ) -> str: ...
+    def _export_to_c(self, out_ptr: int) -> None: ...
+    @classmethod
+    def _import_from_c(cls, in_ptr: int) -> Schema: ...
+    def __arrow_c_schema__(self) -> Any: ...
+    @staticmethod
+    def _import_from_c_capsule(schema: Any) -> Schema: ...
+
+def unify_schemas(
+    schemas: Iterable[Schema],
+    *,
+    promote_options: Literal["default", "permissive"] = "default",
+) -> Schema: ...
+@overload
+def field(
+    name: SupportsArrowSchema,
+    type: None = None,
+    nullable: bool | None = None,
+    metadata: _KeyValueMetadataInput | KeyValueMetadata = None,
+) -> Field[Any]: ...
+@overload
+def field(
+    name: _FieldName,
+    type: _DataTypeT,
+    nullable: bool | None = None,
+    metadata: _KeyValueMetadataInput | KeyValueMetadata = None,
+) -> Field[_DataTypeT]: ...
+@overload
+def field(
+    name: _FieldName,
+    type: _DataTypeAliasInput,
+    nullable: bool | None = None,
+    metadata: _KeyValueMetadataInput | KeyValueMetadata = None,
+) -> Field[Any]: ...
+def null() -> NullType: ...
+def bool_() -> BoolType: ...
+def uint8() -> UInt8Type: ...
+def int8() -> Int8Type: ...
+def uint16() -> UInt16Type: ...
+def int16() -> Int16Type: ...
+def uint32() -> UInt32Type: ...
+def int32() -> Int32Type: ...
+def int64() -> Int64Type: ...
+def uint64() -> UInt64Type: ...
+def timestamp(
+    unit: _Unit | str, tz: _Tz | None = None
+) -> TimestampType[_Unit, _Tz]: ...

Review Comment:
   The timezone is constrained to str or None here, but timestamp() in 
types.pxi also accepts datetime.tzinfo objects and bytes, converting them to 
timezone strings. Valid calls such as `timestamp('us', 
tz=datetime.timezone.utc)` are therefore rejected by this stub. Please include 
those inputs and describe their resulting `.tz` as str.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to