Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
9404659
Add internal types and helpers
rok Dec 21, 2025
5f7cdc8
Update python/pyarrow-stubs/pyarrow/_stubs_typing.pyi
rok Feb 9, 2026
610c48d
post rebase changes
rok Mar 9, 2026
7bd5d7c
linting and formatting
rok Mar 13, 2026
2f1a0ba
add pyi check to pre-commit
rok Mar 13, 2026
42ab12d
ruff format
rok Mar 13, 2026
675ae64
Apply suggestions from code review
rok Mar 16, 2026
14b924b
review feedback
rok Mar 16, 2026
4ec84e4
Decimals should be covariant
rok Mar 16, 2026
235c90d
fix editable install collisions
rok Mar 17, 2026
046fd11
review feedback
rok Mar 17, 2026
0454fb9
move placeholder
rok Mar 17, 2026
e6bc69b
post rebase
rok May 6, 2026
a33f327
Apply suggestions from code review
rok Jun 14, 2026
ca9441c
Update python/pyarrow-stubs/pyarrow/_types.pyi
rok Jun 14, 2026
dbc4e40
update
rok Jun 14, 2026
d2cc882
lint
rok Jun 14, 2026
dce40ae
more covariant types
rok Jun 14, 2026
f278750
Apply suggestions from code review
rok Jun 14, 2026
ca7c54e
lint
rok Jun 14, 2026
c032596
Add type stubs for core data structures
rok Feb 9, 2026
3df0567
temporary
rok Mar 10, 2026
c990f32
post rebase
rok Jun 14, 2026
cffdf24
Use plain classes and unions instead of generics in stubs
rok Oct 26, 2026
bb8681e
Add tests for the type annotations of public APIs
rok Oct 26, 2026
376407d
Add placeholder stub for pyarrow._ipc
rok Oct 26, 2026
bc27123
Skip type checking of modules that aren't annotated yet
rok Oct 26, 2026
ddd9cb4
Fix stub annotations that don't match the runtime
rok Oct 26, 2026
92250c5
Fix test_timedelta_overflow for sub-unit truncation
rok Oct 26, 2026
9636e5c
Keep RecordBatchReader as Any until it gets annotated
rok Oct 26, 2026
4abbecc
Use TypeGuard for is_fixed_size_binary()
rok Oct 26, 2026
325ed1d
Test indexing of arrays, chunked arrays and tables
rok Oct 26, 2026
26fa0e0
Add manual pre-commit hooks for linting and type checking the stubs
rok Oct 26, 2026
7d7a9fd
Add _compute.pyi and reportMissingModuleSource
AlenkaF Oct 27, 2026
12ea05f
Fix linter error
AlenkaF Oct 27, 2026
25a866b
Stubs ruff-format
tadeja Oct 27, 2026
94021e0
Move to __lib/ to fix Module errors for pa.array/table/scalar
tadeja Oct 28, 2026
ae3cad1
Move remaining ipc/types.pyi to __lib/
tadeja Oct 29, 2026
ca5eb2e
Review type.pyi
AlenkaF Oct 30, 2026
d18e949
Add typing_extensions to dependency-groups test
tadeja Oct 31, 2026
cf32c2e
Array staticmethod _import_from_c_capsule/device and test
tadeja Oct 31, 2026
516e8a9
Add hash stubs to ExtensionType and KeyValueMetadata
AlenkaF Nov 2, 2026
401c3c9
Simplified array inputs, ignore opt imports, fix DLPack signature and…
tadeja Nov 2, 2026
368a6dc
Change Array.equals Any to Array and add tests
tadeja Nov 3, 2026
91c3f0b
Review table.pyi
AlenkaF Nov 5, 2026
1589c4c
Update ci/scripts/python_test_type_annotations.sh
AlenkaF Nov 6, 2026
f6892cd
Remove sys.version_info >= (3, 11) check
AlenkaF Nov 6, 2026
a1163e2
Assert struct array
AlenkaF Nov 6, 2026
e51b18e
Add test for list_
AlenkaF Nov 6, 2026
d297339
Fix CI ty check failure latest v0.99
tadeja Nov 6, 2026
9dcdac6
More cases for test_table.py
tadeja Nov 6, 2026
2f49f15
table.pyi: Allow array protocols for inputs. Specify append_column
tadeja Nov 6, 2026
3301e77
Update python/pyarrow-stubs/tests/test_types.py
AlenkaF Nov 9, 2026
bf4a592
Add test for Field.metadata
AlenkaF Nov 9, 2026
a9cdd20
Remove sys.version_info >= (3, 11) on all files
AlenkaF Nov 9, 2026
905aa33
Update python/pyarrow-stubs/pyarrow/__lib/error.pyi
AlenkaF Nov 9, 2026
a94a96e
More cases for test_array.py
tadeja Nov 9, 2026
319b5c5
Review array.pyi
tadeja Nov 9, 2026
b642a3b
Apply AlenkaF's suggestions and add tests
tadeja Nov 9, 2026
4b7dc90
Apply batched suggestions from code review
rok Nov 9, 2026
41cc576
Move stubs to a flat pyarrow-stubs package and tests to pyarrow-stubs…
rok Nov 9, 2026
82f4e53
Address review comments on type checking setup and stubs
rok Nov 9, 2026
79a8f15
Re-export annotated pyarrow.lib names in pyarrow and use pa.* in tests
rok Nov 9, 2026
046f15f
Run the ruff hooks for the type stubs on commit
rok Nov 10, 2026
5caf254
Install the stub type checkers with uvx from requirements-stubs.txt
rok Nov 10, 2026
c093920
Mark MonthDayNano as final in the type stubs
rok Nov 10, 2026
cce4648
Remove the partial marker so that pyright checks the type stubs
rok Nov 10, 2026
61c7c26
Remove the import ignores from the type stubs
rok Nov 10, 2026
6ed844e
Add tensor.pyi updates and tensor test file
AlenkaF Nov 10, 2026
6a4e614
Update stubtest allowlist
AlenkaF Nov 10, 2026
da3465a
Update _KeyValueMetadataInput
AlenkaF Nov 10, 2026
86fe422
Update __contains__ and __getitem__ in KeyValueMetadata
AlenkaF Nov 10, 2026
5ea9439
test_array.py unused type-ignore
tadeja Nov 10, 2026
42927be
Update mask arg in array()
AlenkaF Nov 10, 2026
724d480
Update Mask alias
AlenkaF Nov 10, 2026
1335885
Update python/pyarrow-stubs/__lib/array.pyi
AlenkaF Nov 10, 2026
bb913a8
pyarrow/tests/test_*.py remove/update ignores
tadeja Nov 10, 2026
17e3a08
Clean up the type stubs
rok Nov 10, 2026
e054197
Tidy the type checking setup
rok Nov 10, 2026
4be907a
Remove the type checker accommodations from the PyArrow tests
rok Nov 10, 2026
2d3f85c
Import scipy directly in the tensor type stub test
rok Nov 10, 2026
6ae817e
Fix the type stub mismatches that stubtest skipped
rok Nov 10, 2026
ff91bcf
Make the optional import fallbacks in the PyArrow tests pass mypy
rok Nov 10, 2026
c7d1d97
Apply suggestion from @rok
rok Nov 10, 2026
d027724
Order imports and unify importing from pyarrow.lib
AlenkaF Nov 10, 2026
9fc46cd
Fix the CI due to changes in imports
AlenkaF Nov 10, 2026
a8cc1ae
Update Indiced type alias
AlenkaF Nov 10, 2026
96ba7b9
array.pyi: from_pandas type to accept str
tadeja Nov 10, 2026
0e446fc
test_array.py: test from_pandas str and take Array indices
tadeja Nov 10, 2026
faf04a9
array.pyi: from_buffers accept Collection instead of Sequence
tadeja Nov 10, 2026
4a899eb
Use TYPE_CHECKING for invalid usage in stub tests and fix review find…
rok Nov 10, 2026
91ae655
Fix type checker errors in stub tests
rok Nov 10, 2026
d2462bd
array.pyi: nulls/repeat return Array instead of ArrayLike
tadeja Nov 10, 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
6 changes: 6 additions & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ jobs:
- name: Run pre-commit
run: |
pre-commit run --all-files --color=always --show-diff-on-failure
- name: Run pre-commit for type stubs
env:
# The previous step already ran the ruff hooks.
SKIP: ruff-check,ruff-format
run: |
pre-commit run --hook-stage manual python-stubs --all-files --color=always --show-diff-on-failure

release:
name: Source Release and Merge Script on ${{ matrix.runs-on }}
Expand Down
64 changes: 64 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,70 @@ repos:
(
?^python/pyarrow/vendored/|
)
# Linting, formatting and type checking of the type stubs. The type checkers
# don't run on commit, only on request (the Lint CI job runs them too):
# pre-commit run --hook-stage manual python-stubs --all-files
# The type checkers run with uvx, in the environment of
# python/requirements-stubs.txt. They don't need PyArrow installed: the
# modules that aren't annotated yet, e.g. pyarrow.compute, are treated as Any
# (see python/pyproject.toml).
- 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)
args:
- "--fix"
files: >-
^python/pyarrow-stubs/
types:
- pyi
- id: ruff-format
alias: python-stubs
name: Python Stubs Format (ruff)
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 uvx --with-requirements requirements-stubs.txt mypy"
pass_filenames: false
files: >-
^python/(pyarrow-stubs(-tests)?/|(pyproject\.toml|requirements-stubs\.txt)$)
additional_dependencies: &uv
- uv==0.13.7
- id: pyright
alias: python-stubs
name: Python Stubs Type Check (pyright)
stages:
- manual
language: python
entry: uvx --with-requirements python/requirements-stubs.txt pyright -p python/pyproject.toml
pass_filenames: false
files: >-
^python/(pyarrow-stubs(-tests)?/|(pyproject\.toml|requirements-stubs\.txt)$)
additional_dependencies: *uv
- id: ty
alias: python-stubs
name: Python Stubs Type Check (ty)
stages:
- manual
language: python
# Unset the VIRTUAL_ENV of the hook, so that ty uses the environment
# of uvx.
entry: env -u VIRTUAL_ENV uvx --with-requirements python/requirements-stubs.txt ty check --project python
pass_filenames: false
files: >-
^python/(pyarrow-stubs(-tests)?/|(pyproject\.toml|requirements-stubs\.txt)$)
additional_dependencies: *uv
- repo: https://github.com/MarcoGorelli/cython-lint
rev: v0.21.1
hooks:
Expand Down
16 changes: 11 additions & 5 deletions ci/scripts/python_test_type_annotations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@ if [ -n "${ARROW_PYTHON_VENV:-}" ]; then
. "${ARROW_PYTHON_VENV}/bin/activate"
fi

# Install library stubs. Note some libraries contain their own type hints so they need to be installed.
pip install fsspec pandas-stubs scipy-stubs types-cffi types-psutil types-requests types-python-dateutil
cd "${pyarrow_dir}"

# Install type checkers
pip install mypy pyright ty
# Install type checkers and library stubs. Note some libraries contain their
# own type hints so they need to be installed.
pip install -r requirements-stubs.txt

# Run type checkers
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 expected annotated types against the runtime types.
pytest -r s pyarrow-stubs-tests

# Check that the stubs match the signatures of the installed PyArrow.
python scripts/run_stubtest.py
4 changes: 2 additions & 2 deletions ci/scripts/python_wheel_validate_contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def validate_wheel(path):
), "pyarrow/py.typed is present in the wheel."

source_root = Path(__file__).resolve().parents[2]
stubs_dir = source_root / "python" / "pyarrow-stubs" / "pyarrow"
stubs_dir = source_root / "python" / "pyarrow-stubs"
assert stubs_dir.exists(), f"Stub source directory not found: {stubs_dir}"

expected_stub_files = {
Expand All @@ -75,7 +75,7 @@ def validate_wheel(path):
}

assert not (wheel_stub_files == expected_stub_files), (
"Wheel .pyi files do not differ from python/pyarrow-stubs/pyarrow.\n"
"Wheel .pyi files do not differ from python/pyarrow-stubs.\n"
f"Missing in wheel: {sorted(expected_stub_files - wheel_stub_files)}\n"
f"Unexpected in wheel: {sorted(wheel_stub_files - expected_stub_files)}"
)
Expand Down
88 changes: 86 additions & 2 deletions docs/source/developers/python/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,41 @@ Type Checking
=============

PyArrow provides type stubs (``*.pyi`` files) for static type checking. These
stubs are located in the ``pyarrow-stubs/`` directory and are automatically
included in the distributed wheel packages.
stubs are located in the ``pyarrow-stubs/`` directory, which is laid out as a
:pep:`561` stub-only package for ``pyarrow``, so type checkers and IDEs pick
it up from the ``arrow/python`` directory. The stubs aren't included in the
distributed wheel packages until they are complete (see
`GH-49831 <https://github.com/apache/arrow/issues/49831>`_).

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 type checkers and stub packages pinned in ``requirements-stubs.txt`` (with
uv). This also lints and formats the stubs with ruff:

.. code-block::

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

The type checkers only run on request, while ruff also runs on commit. The
Lint CI job runs all of them as well. CI also runs the type checkers, the type
stub tests and stubtest (see below) against the PyArrow it builds, with
``ci/scripts/python_test_type_annotations.sh``.

To run a type checker directly instead, install the pinned type checkers and
stub packages in your PyArrow development environment:

.. code-block::

$ pip install -r requirements-stubs.txt

Checking the PyArrow tests also needs their dependencies from
``requirements-test.txt``.

**mypy**

To run mypy on the PyArrow codebase:
Expand All @@ -124,6 +150,12 @@ To run mypy on the PyArrow codebase:
$ mypy

The mypy configuration is in the ``[tool.mypy]`` section of ``pyproject.toml``.
mypy only uses the stubs when it checks them too, so pass ``pyarrow-stubs``
along with any other files you check:

.. code-block::

$ mypy pyarrow-stubs pyarrow/tests/test_array.py

**pyright**

Expand All @@ -145,6 +177,20 @@ To run ty (note: currently only partially configured):
$ cd arrow/python
$ ty check

**stubtest**

mypy's stubtest compares the stubs with the signatures of the installed
PyArrow. Run it with:

.. code-block::

$ cd arrow/python
$ python scripts/run_stubtest.py

Stubs that aren't written yet are skipped. The known mismatches are listed in
``pyarrow-stubs-tests/stubtest_allowlist.txt``; remove the entries of the
mismatches you fix, as stubtest fails on unused entries.

Maintaining Type Stubs
-----------------------

Expand All @@ -169,6 +215,44 @@ 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

from typing import TYPE_CHECKING

import pyarrow as pa

from _check import assert_type, check

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

if TYPE_CHECKING:
pa.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

Code under ``if TYPE_CHECKING:`` is never executed. It holds the usage that
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. ty
doesn't honor mypy error codes, so it doesn't check invalid usage.

Doctest
=======

Expand Down
4 changes: 2 additions & 2 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1077,15 +1077,15 @@ endif()
#
# TODO(GH-32609): Reintroduce type stubs into PyArrow wheels.
# GH-49831: Temporarily do not install pyarrow-stubs into wheels.
# Stubs live in pyarrow-stubs/pyarrow/ during development but are installed
# Stubs live in pyarrow-stubs/ during development but are installed
# alongside the package so type checkers can find them (PEP 561).
# The stubs are currently incomplete, and some type checkers consume .pyi files
# even without the py.typed marker. Re-enable this when the stubs are complete.
if(PYARROW_REQUIRE_STUB_DOCSTRINGS)
message(FATAL_ERROR "PYARROW_REQUIRE_STUB_DOCSTRINGS cannot be used while "
"pyarrow-stubs are omitted from wheels (GH-49831).")
endif()
# set(PYARROW_STUBS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/pyarrow-stubs/pyarrow")
# set(PYARROW_STUBS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/pyarrow-stubs")
# if(EXISTS "${PYARROW_STUBS_SOURCE_DIR}")
# install(DIRECTORY "${PYARROW_STUBS_SOURCE_DIR}/"
# DESTINATION "."
Expand Down
48 changes: 48 additions & 0 deletions python/pyarrow-stubs-tests/_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 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.

"""Helpers for the type annotation tests in this directory.

Each test pairs a static and a runtime check of the same expression::

check(assert_type(pa.list_(pa.int32()), pa.ListType), pa.ListType)

``assert_type`` is verified by every type checker we run in CI (mypy,
pyright and ty) and is a no-op at runtime. ``check`` runs under pytest and
verifies that the value really is an instance of the given class, so the
stubs can't drift away from what pyarrow actually returns.

Code that type checkers should *reject* goes under ``if TYPE_CHECKING:``
with a ``# type: ignore[<mypy code>]`` comment. The block never runs, and
mypy and pyright are configured to report unused ignores in these tests, so
the test fails if the usage stops being an error.
"""

from typing import Any, TypeVar, assert_type

__all__ = ["assert_type", "check"]

_T = TypeVar("_T")


def check(actual: _T, klass: type[Any] | tuple[type[Any], ...]) -> _T:
"""Assert that ``actual`` is an instance of ``klass`` and return it."""
if not isinstance(actual, klass):
raise AssertionError(
f"Expected an instance of {klass}, got {type(actual)}: {actual!r}"
)
return actual
37 changes: 37 additions & 0 deletions python/pyarrow-stubs-tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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 sys
import types

import pyarrow as pa


def _stub_only_type(name: str) -> type[pa.DataType]:
# The factories of the stub-only DataType subclasses (Int32Type, ...)
# return plain DataType instances at runtime.
if name.endswith("Type") and not name.startswith("_"):
return pa.DataType
raise AttributeError(name)


# pyarrow._stubs_typing only exists in the type stubs. Provide it at runtime,
# so that the tests can import the stub-only types without a TYPE_CHECKING
# block.
_stubs_typing = types.ModuleType("pyarrow._stubs_typing")
setattr(_stubs_typing, "__getattr__", _stub_only_type)
sys.modules["pyarrow._stubs_typing"] = _stubs_typing
30 changes: 30 additions & 0 deletions python/pyarrow-stubs-tests/stubtest_allowlist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 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.

# Mismatches between the type stubs and the runtime that stubtest skips, one
# regex per line; see scripts/run_stubtest.py. Unused entries fail stubtest,
# so remove the entries of the mismatches you fix. Print the entries for the
# current mismatches with
# python scripts/run_stubtest.py --generate-allowlist

# Stub-only modules. The pyarrow.__lib modules are checked against
# pyarrow.lib, which has no __all__.
pyarrow\._stubs_typing
pyarrow\.__lib\.[a-z]+\.__all__

# The __init__ of Cython extension types takes (*args, **kwargs) at runtime.
pyarrow\.__lib\.[a-z]+\.[A-Za-z_]+\.__init__
Loading
Loading