Skip to content

Commit 293b7e2

Browse files
kkollsgaclaude
andcommitted
Add stubtest CI job for type annotation validation
Add stubtest to CI for validating type stubs against runtime behavior. This helps catch type annotation regressions early. - Add stubtest job to ci-additional.yaml (non-blocking with continue-on-error) - Create allowlist for known acceptable differences (TYPE_CHECKING imports, etc.) - Tests core modules: dataarray, dataset, variable Refs #11086 Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e095d9f commit 293b7e2

3 files changed

Lines changed: 225 additions & 0 deletions

File tree

.github/workflows/ci-additional.yaml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,57 @@ jobs:
184184
name: codecov-umbrella
185185
fail_ci_if_error: false
186186

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

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

4650
.. _whats-new.2026.01.0:
4751

0 commit comments

Comments
 (0)