Skip to content

Commit 8f6605e

Browse files
kkollsgaclaudedcherian
authored
Add stubtest to CI for type annotation validation (#11124)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Deepak Cherian <dcherian@users.noreply.github.com>
1 parent 5e31466 commit 8f6605e

3 files changed

Lines changed: 220 additions & 0 deletions

File tree

‎.github/workflows/ci-additional.yaml‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,52 @@ jobs:
188188
name: codecov-umbrella
189189
fail_ci_if_error: false
190190

191+
stubtest:
192+
name: Stubtest
193+
runs-on: "ubuntu-latest"
194+
needs: [detect-ci-trigger, cache-pixi-lock]
195+
# Phase 1: Non-blocking (informational only)
196+
# Change to 'false' once stubtest is stable to make it required
197+
continue-on-error: true
198+
defaults:
199+
run:
200+
shell: bash -l {0}
201+
env:
202+
PIXI_ENV: test-py313-with-typing
203+
204+
steps:
205+
- uses: actions/checkout@v6
206+
with:
207+
fetch-depth: 0
208+
209+
- name: Restore cached pixi lockfile
210+
uses: Parcels-code/pixi-lock/restore@9a2866f8258b87a3c616d5ad7d51c6cd853df78b
211+
with:
212+
cache-key: ${{ needs.cache-pixi-lock.outputs.cache-key }}
213+
- uses: prefix-dev/setup-pixi@v0.9.4
214+
with:
215+
pixi-version: ${{ needs.cache-pixi-lock.outputs.pixi-version }}
216+
cache: true
217+
environments: ${{ env.PIXI_ENV }}
218+
cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
219+
220+
- name: Version info
221+
run: |
222+
pixi run -e ${{env.PIXI_ENV}} -- python xarray/util/print_versions.py
223+
224+
- name: Install type stubs
225+
run: |
226+
pixi run -e ${{env.PIXI_ENV}} -- python -m mypy --install-types --non-interactive xarray/ || true
227+
228+
- name: Run stubtest (core modules)
229+
run: |
230+
pixi run -e ${{env.PIXI_ENV}} -- python -m mypy.stubtest \
231+
xarray.core.dataarray \
232+
xarray.core.dataset \
233+
xarray.core.variable \
234+
--mypy-config-file pyproject.toml \
235+
--allowlist _stubtest/allowlist.txt
236+
191237
pyright:
192238
name: Pyright | ${{ matrix.pixi-env }}"
193239
runs-on: "ubuntu-latest"

‎_stubtest/allowlist.txt‎

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Stubtest Allowlist for xarray
2+
# ================================
3+
# This file contains patterns for symbols that stubtest reports as errors
4+
# but are working correctly at runtime.
5+
#
6+
# Format: Each line is a regex pattern matching fully qualified names to ignore.
7+
# See: https://mypy.readthedocs.io/en/stable/stubtest.html#allowlist
8+
9+
# =============================================================================
10+
# typing module re-exports (metaclass/signature differences)
11+
# =============================================================================
12+
13+
# Any has metaclass differences between stub and runtime
14+
xarray\.core\.(dataarray|dataset|variable)\.Any$
15+
xarray\.core\.(dataarray|dataset|variable)\.Any\.__new__$
16+
17+
# Callable also has metaclass differences
18+
xarray\.core\.(dataarray|dataset|variable)\.Callable$
19+
20+
# Self is a TYPE_CHECKING import
21+
xarray\.core\.(dataarray|variable)\.Self$
22+
23+
# TypeVar has __mro_entries__ at runtime but not in stubs
24+
xarray\.core\.dataarray\.TypeVar\.__mro_entries__$
25+
26+
# =============================================================================
27+
# TYPE_CHECKING imports - not present at runtime
28+
# =============================================================================
29+
30+
# Type aliases from xarray.core.types
31+
xarray\.core\.(dataarray|dataset|variable)\.Dims$
32+
xarray\.core\.(dataarray|dataset)\.DatetimeLike$
33+
xarray\.core\.(dataarray|dataset)\.DatetimeUnitOptions$
34+
xarray\.core\.(dataarray|dataset)\.ErrorOptions$
35+
xarray\.core\.(dataarray|dataset|variable)\.ErrorOptionsWithWarn$
36+
xarray\.core\.(dataarray|dataset)\.GroupIndices$
37+
xarray\.core\.(dataarray|dataset)\.GroupInput$
38+
xarray\.core\.(dataarray|dataset)\.Grouper$
39+
xarray\.core\.(dataarray|dataset)\.InterpOptions$
40+
xarray\.core\.(dataarray|dataset)\.NetcdfWriteModes$
41+
xarray\.core\.(dataarray|dataset|variable)\.PadModeOptions$
42+
xarray\.core\.(dataarray|dataset|variable)\.PadReflectOptions$
43+
xarray\.core\.(dataarray|dataset|variable)\.QuantileMethods$
44+
xarray\.core\.(dataarray|dataset)\.QueryEngineOptions$
45+
xarray\.core\.(dataarray|dataset)\.QueryParserOptions$
46+
xarray\.core\.(dataarray|dataset)\.ReindexMethodOptions$
47+
xarray\.core\.(dataarray|dataset)\.ResampleCompatible$
48+
xarray\.core\.(dataarray|dataset)\.Resampler$
49+
xarray\.core\.(dataarray|dataset)\.SideOptions$
50+
xarray\.core\.(dataarray|dataset)\.CoarsenBoundaryOptions$
51+
xarray\.core\.(dataarray|dataset)\.ZarrWriteModes$
52+
xarray\.core\.(dataarray|dataset)\.ZarrStore$
53+
xarray\.core\.(dataarray|dataset)\.ZarrStoreLike$
54+
xarray\.core\.(dataarray|dataset)\.ArrayLike$
55+
xarray\.core\.dataset\.CFCalendar$
56+
xarray\.core\.dataset\.CombineAttrsOptions$
57+
xarray\.core\.dataset\.CompatOptions$
58+
xarray\.core\.dataset\.JoinOptions$
59+
xarray\.core\.dataset\.CoercibleMapping$
60+
xarray\.core\.dataset\.CoercibleValue$
61+
xarray\.core\.dataset\.DataVars$
62+
xarray\.core\.dataset\.DsCompatible$
63+
64+
# TypeVars
65+
xarray\.core\.(dataarray|dataset)\.T_ChunkDimFreq$
66+
xarray\.core\.(dataarray|dataset)\.T_ChunksFreq$
67+
xarray\.core\.(dataarray|dataset|variable)\.T_Chunks$
68+
xarray\.core\.(dataarray|dataset)\.T_NetcdfEngine$
69+
xarray\.core\.(dataarray|dataset)\.T_NetcdfTypes$
70+
xarray\.core\.(dataarray|dataset)\.T_Xarray$
71+
xarray\.core\.dataarray\.T_XarrayOther$
72+
xarray\.core\.dataset\.T_DatasetPadConstantValues$
73+
xarray\.core\.variable\.T_DuckArray$
74+
xarray\.core\.variable\.T_VarPadConstantValues$
75+
76+
# External library types (dask, delayed, etc.)
77+
xarray\.core\.(dataarray|dataset|variable)\.ChunkManagerEntrypoint$
78+
xarray\.core\.(dataarray|dataset)\.DaskDataFrame$
79+
xarray\.core\.(dataarray|dataset)\.Delayed$
80+
xarray\.core\.dataset\.AbstractDataStore$
81+
xarray\.core\.dataarray\.iris_Cube$
82+
83+
# DataArray TYPE_CHECKING import in dataset module
84+
xarray\.core\.dataset\.DataArray$
85+
86+
# NamedArray TYPE_CHECKING import
87+
xarray\.core\.variable\.NamedArray$
88+
89+
# =============================================================================
90+
# GroupBy/Rolling/Coarsen/Weighted/Resample classes (TYPE_CHECKING imports)
91+
# =============================================================================
92+
93+
xarray\.core\.dataarray\.DataArrayGroupBy$
94+
xarray\.core\.dataarray\.DataArrayCoarsen$
95+
xarray\.core\.dataarray\.DataArrayRolling$
96+
xarray\.core\.dataarray\.DataArrayWeighted$
97+
xarray\.core\.dataarray\.DataArrayResample$
98+
xarray\.core\.dataset\.DatasetGroupBy$
99+
xarray\.core\.dataset\.DatasetCoarsen$
100+
xarray\.core\.dataset\.DatasetRolling$
101+
xarray\.core\.dataset\.DatasetWeighted$
102+
xarray\.core\.dataset\.DatasetResample$
103+
104+
# =============================================================================
105+
# CFTimeIndex properties - read-only at runtime
106+
# =============================================================================
107+
108+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.date_type$
109+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.day$
110+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.dayofweek$
111+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.dayofyear$
112+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.days_in_month$
113+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.hour$
114+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.microsecond$
115+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.minute$
116+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.month$
117+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.second$
118+
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.year$
119+
120+
# =============================================================================
121+
# Plot accessors and methods
122+
# =============================================================================
123+
124+
xarray\.core\.dataarray\.DataArray\.plot$
125+
xarray\.core\.(dataarray|dataset)\.Dataset\.plot$
126+
xarray\.core\.dataarray\.DataArrayPlotAccessor\.(contour|contourf|imshow|line|pcolormesh|scatter|step|surface)$
127+
xarray\.core\.dataset\.DatasetPlotAccessor\.(quiver|scatter|streamplot)$
128+
129+
# =============================================================================
130+
# __array__ method - complex numpy protocol
131+
# =============================================================================
132+
133+
xarray\.core\.(dataarray|dataset)\.Dataset\.__array__$
134+
135+
# =============================================================================
136+
# Mapping/MutableMapping/Sequence/Iterable protocol methods
137+
# =============================================================================
138+
139+
xarray\.core\.(dataarray|dataset|variable)\.Mapping\.get$
140+
xarray\.core\.(dataarray|dataset|variable)\.Mapping\.__reversed__$
141+
xarray\.core\.(dataarray|dataset|variable)\.Sequence\.index$
142+
xarray\.core\.(dataarray|dataset)\.Iterable\.__class_getitem__$
143+
xarray\.core\.(dataarray|dataset)\.MutableMapping\.(pop|setdefault)$
144+
xarray\.core\.(dataarray|dataset)\.PathLike\.__class_getitem__$
145+
xarray\.core\.dataset\.FrozenMappingWarningOnValuesAccess\.get$
146+
147+
# Number.__hash__
148+
xarray\.core\.dataset\.Number\.__hash__$
149+
150+
# =============================================================================
151+
# IO protocol (typing.IO)
152+
# =============================================================================
153+
154+
xarray\.core\.dataset\.IO\.(closed|mode|name|read|readline|readlines|seek|truncate|write|writelines|__iter__|__next__)$
155+
156+
# =============================================================================
157+
# Variable/IndexVariable methods inherited from numpy-like interface
158+
# =============================================================================
159+
160+
xarray\.core\.(dataarray|dataset|variable)\.Variable\.(item|searchsorted)$
161+
xarray\.core\.(dataarray|dataset|variable)\.IndexVariable\.(item|searchsorted)$
162+
xarray\.core\.dataarray\.DataArray\.(item|searchsorted)$
163+
xarray\.core\.dataarray\.DataArrayArithmetic\.(item|searchsorted)$
164+
xarray\.core\.variable\.VariableArithmetic\.(item|searchsorted)$
165+
166+
# =============================================================================
167+
# Subclass pattern for Variable
168+
# =============================================================================
169+
170+
xarray\.core\.variable\.<subclass.*

‎doc/whats-new.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ Documentation
4545
Internal Changes
4646
~~~~~~~~~~~~~~~~
4747

48+
- Add stubtest configuration and allowlist for validating type annotations against
49+
runtime behavior. This enables CI integration for type stub validation and helps
50+
prevent type annotation regressions (:issue:`11086`).
51+
By `Kristian Kollsgård <https://github.com/kkollsga>`_.
4852

4953
.. _whats-new.2026.02.0:
5054

0 commit comments

Comments
 (0)