Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ee419b0
Add internal types and helpers
rok Dec 21, 2025
b09050f
Update python/pyarrow-stubs/pyarrow/_stubs_typing.pyi
rok Feb 9, 2026
28a44c7
post rebase changes
rok Mar 9, 2026
3d7a713
linting and formatting
rok Mar 13, 2026
fb50721
add pyi check to pre-commit
rok Mar 13, 2026
fc48948
ruff format
rok Mar 13, 2026
9b03d17
Apply suggestions from code review
rok Mar 16, 2026
3874966
review feedback
rok Mar 16, 2026
32e439d
Decimals should be covariant
rok Mar 16, 2026
93d807e
fix editable install collisions
rok Mar 17, 2026
558321c
review feedback
rok Mar 17, 2026
6a1d410
move placeholder
rok Mar 17, 2026
de68c89
post rebase
rok May 6, 2026
e3105e3
Apply suggestions from code review
rok Jun 14, 2026
de7a5b6
Update python/pyarrow-stubs/pyarrow/_types.pyi
rok Jun 14, 2026
5d920e9
update
rok Jun 14, 2026
f7573ac
lint
rok Jun 14, 2026
a0c8c7a
more covariant types
rok Jun 14, 2026
a05d894
Apply suggestions from code review
rok Jun 14, 2026
821e914
lint
rok Jun 14, 2026
3003457
Add type stubs for core data structures
rok Feb 9, 2026
8886730
temporary
rok Mar 10, 2026
5ee863c
post rebase
rok Jun 14, 2026
4f6c101
Use plain classes and unions instead of generics in stubs
rok Oct 26, 2026
9a1185c
Add tests for the type annotations of public APIs
rok Oct 26, 2026
168c7c4
Add placeholder stub for pyarrow._ipc
rok Oct 26, 2026
c347f7f
Skip type checking of modules that aren't annotated yet
rok Oct 26, 2026
0e55c81
Fix stub annotations that don't match the runtime
rok Oct 26, 2026
b096b3b
Fix test_timedelta_overflow for sub-unit truncation
rok Oct 26, 2026
147e021
Keep RecordBatchReader as Any until it gets annotated
rok Oct 26, 2026
67aeb33
Use TypeGuard for is_fixed_size_binary()
rok Oct 26, 2026
440fbee
Test indexing of arrays, chunked arrays and tables
rok Oct 26, 2026
a72e35b
Add manual pre-commit hooks for linting and type checking the stubs
rok Oct 26, 2026
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
85 changes: 85 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,100 @@ repos:
- id: flake8
alias: python
name: Python Lint
additional_dependencies:
- flake8-pyi
args:
- "--config"
- "python/setup.cfg"
- "--extend-select"
- "Y"
- "--per-file-ignores"
- "python/pyarrow-stubs/pyarrow/*.pyi:E301,E302,E305,E701,F821"
files: >-
^(c_glib|dev|python)/
types:
- file
types_or:
- python
- pyi
exclude: >-
(
?^python/pyarrow/vendored/|
)
# Linting, formatting and type checking of the type stubs. These hooks only
# run on request, not on commit or in the existing CI jobs:
# pre-commit run --hook-stage manual python-stubs --all-files
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.17.2
hooks:
- id: ruff-check
alias: python-stubs
name: Python Stubs Lint (ruff)
stages:
- manual
args:
- "--fix"
files: >-
^python/pyarrow-stubs/
types:
- pyi
- id: ruff-format
alias: python-stubs
name: Python Stubs Format (ruff)
stages:
- manual
files: >-
^python/pyarrow-stubs/
types:
- pyi
- repo: local
hooks:
- id: mypy
alias: python-stubs
name: Python Stubs Type Check (mypy)
stages:
- manual
language: python
entry: bash -c "cd python && exec mypy"
pass_filenames: false
files: >-
^python/(pyarrow-stubs/|pyproject\.toml$)
additional_dependencies: &python-type-check-dependencies
- fsspec==2026.9.0
- mypy==2.4.0
- numpy==2.4.6
- pandas-stubs==3.0.5.260914
# Provides the modules that aren't annotated yet, e.g. pyarrow.compute.
- pyarrow==26.0.0
- pyright==1.1.414
- scipy-stubs==1.17.1.5
- ty==0.0.92
- types-cffi==2.1.0.20260827
- types-psutil==7.2.2.20260906
- types-python-dateutil==2.9.0.20261021
- types-requests==2.33.0.20261006
- id: pyright
alias: python-stubs
name: Python Stubs Type Check (pyright)
stages:
- manual
language: python
entry: pyright -p python/pyproject.toml
pass_filenames: false
files: >-
^python/(pyarrow-stubs/|pyproject\.toml$)
additional_dependencies: *python-type-check-dependencies
- id: ty
alias: python-stubs
name: Python Stubs Type Check (ty)
stages:
- manual
language: python
entry: ty check --project python
pass_filenames: false
files: >-
^python/(pyarrow-stubs/|pyproject\.toml$)
additional_dependencies: *python-type-check-dependencies
- repo: https://github.com/MarcoGorelli/cython-lint
rev: v0.21.1
hooks:
Expand Down
4 changes: 4 additions & 0 deletions ci/scripts/python_test_type_annotations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ cd "${pyarrow_dir}"
mypy
pyright --stats
ty check --verbose --output-format concise

# The type annotation tests are type checked above; also run them to verify
# the annotated types against the installed pyarrow.
pytest -r s pyarrow-stubs/tests
54 changes: 54 additions & 0 deletions docs/source/developers/python/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ Running Type Checkers
We support multiple type checkers. Their configurations are in
``pyproject.toml``.

The easiest way to run all of them is with ``pre-commit``, which installs
the same versions of the type checkers and stub packages for everyone. This
also lints and formats the stubs with ruff:

.. code-block::

$ pre-commit run --hook-stage manual python-stubs --all-files

These hooks only run on request, not on commit.

To run a type checker directly instead:

**mypy**

To run mypy on the PyArrow codebase:
Expand Down Expand Up @@ -169,6 +181,48 @@ in downstream projects and for users' IDEs.
Note: ``py.typed`` marker file in the ``pyarrow/`` directory indicates to type
checkers that PyArrow supports type checking according to :pep:`561`.

Testing Type Stubs
------------------

The tests in ``pyarrow-stubs/tests/`` pin down the annotated types of public
APIs. Each check pairs :func:`typing.assert_type`, which every type checker
verifies statically, with a runtime ``isinstance`` check:

.. code-block:: python

import pyarrow.lib as lib

from _check import TYPE_CHECKING_INVALID_USAGE, assert_type, check

def test_list_type() -> None:
lst = lib.list_(lib.int32())
check(assert_type(lst, lib.ListType), lib.ListType)
check(assert_type(lst.value_type, lib.DataType), lib.DataType)

if TYPE_CHECKING_INVALID_USAGE:
lib.list_(1) # type: ignore[call-overload]

The type checkers check these files as part of the regular runs above, and
pytest runs them against the installed PyArrow to make sure the stubs agree
with what PyArrow actually returns:

.. code-block::

$ cd arrow/python
$ pytest pyarrow-stubs/tests

Run ``pytest`` directly and pass it the directory: ``python -m pytest`` puts
``arrow/python`` on ``sys.path``, so the source tree would be imported instead
of the installed PyArrow.

Code under ``if TYPE_CHECKING_INVALID_USAGE:`` is never executed. It should
fail type checking, which is asserted with a ``# type: ignore[<mypy code>]``
comment: unused ignores are reported as errors in these tests, so the test
fails once the usage is no longer rejected. Start new test files with a
``# mypy: warn-unused-ignores`` comment to enable this for mypy (pyright is
configured in ``pyproject.toml``). ty doesn't honor mypy error codes, so it
doesn't check invalid usage.

Doctest
=======

Expand Down
6 changes: 0 additions & 6 deletions python/pyarrow-stubs/pyarrow/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@
# specific language governing permissions and limitations
# under the License.

"""Type stubs for PyArrow.

This is a placeholder stub file.
Complete type annotations will be added in subsequent PRs.
"""

from typing import Any

# TODO(GH-48970): remove __getattr__ before release as this
Expand Down
25 changes: 25 additions & 0 deletions python/pyarrow-stubs/pyarrow/_ipc.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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.

# Placeholder stub - complete annotations in future PR.
# At runtime, these symbols are provided by the pyarrow.lib C extension.

from typing import Any, TypeAlias

RecordBatchReader: TypeAlias = Any

def __getattr__(name: str) -> Any: ...
117 changes: 117 additions & 0 deletions python/pyarrow-stubs/pyarrow/_stubs_typing.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# 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

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"]

PyScalar: TypeAlias = (
bool
| int
| float
| Decimal
| str
| bytes
| dt.date
| dt.datetime
| dt.time
| dt.timedelta
)
NumpyScalar: TypeAlias = np.generic[Any]

Mask: TypeAlias = (
Sequence[bool | None] | NDArray[np.bool_] | lib.BooleanArray | ChunkedArray
)
Indices: TypeAlias = (
Sequence[int | None]
| NDArray[np.integer[Any]]
| lib.IntegerArray
| ChunkedArray
)

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: ...

class SupportsArrowArray(Protocol):
def __arrow_c_array__(self, requested_schema: Any = None, /) -> Any: ...

class SupportsArrowDeviceArray(Protocol):
def __arrow_c_device_array__(
self, requested_schema: Any = None, /, **kwargs: Any
) -> Any: ...

class SupportsArrowSchema(Protocol):
def __arrow_c_schema__(self) -> Any: ...

class NullableCollection(Sized, Container[Any], Protocol):
def __iter__(self) -> Iterator[Any]: ...
def __len__(self) -> int: ...
def __contains__(self, item: Any, /) -> bool: ...
Loading
Loading