Skip to content

Improve DataFrame/Series static typing for mypy and Pyright - #2398

Open
cosmicbboy wants to merge 4 commits into
mainfrom
nielsb/fix-series-typing
Open

cosmicbboy wants to merge 4 commits into
mainfrom
nielsb/fix-series-typing

Conversation

@cosmicbboy

@cosmicbboy cosmicbboy commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Extend the mypy plugin to infer column types for df["col"] and df.col on DataFrame[Schema], including inherited fields and bare annotations like label: str
  • Fix generic DataFrame[Schema] typing by sharing the TypeVar from common.py; validate() preserves the runtime dataframe type and is annotated as DataFrame[Self] for static analysis
  • Document mypy follow_imports = silent requirement and add mypy/pyright regression tests

Pyright/Pylance gets schema-level types (DataFrame[Schema] retention through validate()/check_types) via plugin-free generics; column-level inference is a mypy-plugin feature. A generate_typed_dataframe_source() codegen workaround for Pyright column inference was prototyped on this branch and removed as poor devex.

Test plan

  • pytest tests/mypy (30 passed, 13 xfailed)
  • pytest tests/pyright/test_pyright_static_type_checking.py

Closes #1487

Made with Cursor

Extend the mypy plugin with column-level __getitem__ and attribute hooks,
fix generic DataFrame typing, and add a codegen helper for Pyright/Pylance
column inference. Addresses #1487.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.60%. Comparing base (26d54f2) to head (67fb4ce).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2398      +/-   ##
==========================================
- Coverage   90.89%   89.60%   -1.30%     
==========================================
  Files         192      193       +1     
  Lines       17087    18601    +1514     
==========================================
+ Hits        15532    16667    +1135     
- Misses       1555     1934     +379     
Flag Coverage Δ
unit-tests-base-Linux-py3.10 15.38% <40.00%> (-0.03%) ⬇️
unit-tests-base-Linux-py3.11 15.38% <40.00%> (-0.03%) ⬇️
unit-tests-base-Linux-py3.12 15.38% <40.00%> (-0.03%) ⬇️
unit-tests-base-Linux-py3.13 15.38% <40.00%> (-0.03%) ⬇️
unit-tests-base-Linux-py3.14 15.25% <40.00%> (-0.03%) ⬇️
unit-tests-base-Windows-py3.10 15.30% <40.00%> (-0.03%) ⬇️
unit-tests-base-Windows-py3.11 15.30% <40.00%> (-0.03%) ⬇️
unit-tests-base-Windows-py3.12 15.30% <40.00%> (-0.03%) ⬇️
unit-tests-base-Windows-py3.13 15.30% <40.00%> (-0.03%) ⬇️
unit-tests-base-Windows-py3.14 15.17% <40.00%> (-0.03%) ⬇️
unit-tests-base-macOS-py3.10 15.32% <40.00%> (-0.03%) ⬇️
unit-tests-base-macOS-py3.11 15.32% <40.00%> (-0.03%) ⬇️
unit-tests-base-macOS-py3.12 15.32% <40.00%> (-0.03%) ⬇️
unit-tests-base-macOS-py3.13 15.32% <40.00%> (-0.03%) ⬇️
unit-tests-base-macOS-py3.14 15.19% <40.00%> (-0.03%) ⬇️
unit-tests-dask-Linux-py3.10-pandas2.3.3-polars 24.25% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Linux-py3.11-pandas2.3.3-polars 24.25% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Linux-py3.12-pandas2.3.3-polars 24.25% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Linux-py3.13-pandas2.3.3-polars 24.25% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Linux-py3.14-pandas2.3.3-polars 24.16% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Windows-py3.10-pandas2.3.3-polars 24.16% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Windows-py3.11-pandas2.3.3-polars 24.16% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Windows-py3.12-pandas2.3.3-polars 24.16% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Windows-py3.13-pandas2.3.3-polars 24.16% <80.00%> (+0.01%) ⬆️
unit-tests-dask-Windows-py3.14-pandas2.3.3-polars 24.07% <80.00%> (+0.01%) ⬆️
unit-tests-dask-macOS-py3.10-pandas2.3.3-polars 24.18% <80.00%> (+0.01%) ⬆️
unit-tests-fastapi-Linux-py3.11-pandas3.0.0-pydantic2.12.3 24.75% <80.00%> (-0.02%) ⬇️
unit-tests-fastapi-Linux-py3.13-pandas3.0.0-pydantic2.12.3 24.75% <80.00%> (+0.04%) ⬆️
unit-tests-fastapi-Linux-py3.14-pandas2.3.3-pydantic2.12.3 24.60% <80.00%> (-0.01%) ⬇️
unit-tests-fastapi-Windows-py3.10-pandas2.3.3-pydantic2.12.3 24.60% <80.00%> (-0.01%) ⬇️
unit-tests-geopandas-Linux-py3.12-pandas2.3.3-pydantic2.12.3 30.06% <100.00%> (-0.02%) ⬇️
unit-tests-geopandas-Linux-py3.13-pandas3.0.0-pydantic2.12.3 30.06% <100.00%> (-0.02%) ⬇️
unit-tests-hypotheses-Linux-py3.10-pandas2.3.3-pydantic2.12.3 22.48% <40.00%> (+0.01%) ⬆️
unit-tests-hypotheses-Linux-py3.12-pandas2.3.3-pydantic2.12.3 22.48% <40.00%> (?)
unit-tests-hypotheses-Windows-py3.10-pandas2.3.3-pydantic2.12.3 22.41% <40.00%> (-0.01%) ⬇️
unit-tests-hypotheses-Windows-py3.11-pandas2.3.3-pydantic2.12.3 22.41% <40.00%> (-0.02%) ⬇️
unit-tests-hypotheses-Windows-py3.11-pandas3.0.0-pydantic2.12.3 22.45% <40.00%> (+0.08%) ⬆️
unit-tests-ibis-Linux-py3.10-pandas2.3.3-polars 28.84% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Linux-py3.11-pandas2.3.3-polars 28.84% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Linux-py3.12-pandas2.3.3-polars 28.84% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Linux-py3.13-pandas2.3.3-polars 28.84% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Linux-py3.14-pandas2.3.3-polars 28.65% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Windows-py3.10-pandas2.3.3-polars 28.76% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Windows-py3.11-pandas2.3.3-polars 28.76% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Windows-py3.12-pandas2.3.3-polars 28.76% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Windows-py3.13-pandas2.3.3-polars 28.76% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-Windows-py3.14-pandas2.3.3-polars 28.58% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-macOS-py3.10-pandas2.3.3-polars 28.78% <40.00%> (-0.05%) ⬇️
unit-tests-ibis-macOS-py3.11-pandas2.3.3-polars 28.78% <40.00%> (-0.05%) ⬇️
unit-tests-io-Linux-py3.13-pandas2.3.3-pydantic2.12.3 38.12% <40.00%> (-0.01%) ⬇️
unit-tests-io-Windows-py3.11-pandas2.3.3-pydantic2.12.3 37.76% <40.00%> (+0.02%) ⬆️
unit-tests-io-Windows-py3.11-pandas3.0.0-pydantic2.12.3 37.74% <40.00%> (-0.01%) ⬇️
unit-tests-modin-dask-Linux-py3.11-pandas2.3.3-polars 30.69% <80.00%> (-0.01%) ⬇️
unit-tests-modin-dask-Linux-py3.12-pandas2.3.3-polars 30.69% <80.00%> (+0.01%) ⬆️
unit-tests-modin-ray-Linux-py3.10-pandas2.3.3-polars 30.66% <80.00%> (+0.01%) ⬆️
unit-tests-modin-ray-Linux-py3.11-pandas2.3.3-polars 30.66% <80.00%> (+0.01%) ⬆️
unit-tests-modin-ray-Linux-py3.12-pandas2.3.3-polars 30.66% <80.00%> (+0.01%) ⬆️
unit-tests-modin-ray-macOS-py3.10-pandas2.3.3-polars 30.60% <80.00%> (+0.01%) ⬆️
unit-tests-modin-ray-macOS-py3.11-pandas2.3.3-polars 30.60% <80.00%> (+0.01%) ⬆️
unit-tests-narwhals-Linux-py3.10 28.25% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-Linux-py3.11 28.25% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-Linux-py3.12 28.25% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-Linux-py3.13 28.25% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-Linux-py3.14 28.02% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-backend-ibis-py3.10 31.80% <40.00%> (-0.05%) ⬇️
unit-tests-narwhals-backend-ibis-py3.11 31.68% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-backend-ibis-py3.12 31.68% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-backend-ibis-py3.13 31.68% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-backend-ibis-py3.14 31.47% <40.00%> (-0.06%) ⬇️
unit-tests-narwhals-backend-polars-py3.10 34.43% <40.00%> (-0.08%) ⬇️
unit-tests-narwhals-backend-polars-py3.11 34.41% <40.00%> (-0.08%) ⬇️
unit-tests-narwhals-backend-polars-py3.12 34.41% <40.00%> (-0.08%) ⬇️
unit-tests-narwhals-backend-polars-py3.13 34.41% <40.00%> (-0.08%) ⬇️
unit-tests-narwhals-backend-polars-py3.14 34.27% <40.00%> (-0.08%) ⬇️
unit-tests-narwhals-backend-pyspark-py3.10 43.47% <80.00%> (?)
unit-tests-narwhals-backend-pyspark-py3.11 43.50% <80.00%> (?)
unit-tests-pandas-Linux-py3.10-pandas2.3.3-pydantic1.10.11 42.79% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.10-pandas2.3.3-pydantic2.12.3 42.94% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.11-pandas2.3.3-pydantic1.10.11 42.79% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.11-pandas2.3.3-pydantic2.12.3 42.94% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.11-pandas3.0.0-pydantic1.10.11 42.80% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.11-pandas3.0.0-pydantic2.12.3 42.96% <100.00%> (+0.16%) ⬆️
unit-tests-pandas-Linux-py3.12-pandas2.3.3-pydantic1.10.11 42.77% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.12-pandas2.3.3-pydantic2.12.3 42.93% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.12-pandas3.0.0-pydantic1.10.11 42.79% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.12-pandas3.0.0-pydantic2.12.3 42.94% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.13-pandas2.3.3-pydantic1.10.11 42.77% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.13-pandas2.3.3-pydantic2.12.3 42.93% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.13-pandas3.0.0-pydantic1.10.11 42.79% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.13-pandas3.0.0-pydantic2.12.3 42.94% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.14-pandas2.3.3-pydantic2.12.3 42.91% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Linux-py3.14-pandas3.0.0-pydantic2.12.3 42.93% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.10-pandas2.3.3-pydantic1.10.11 42.70% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.10-pandas2.3.3-pydantic2.12.3 42.86% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.11-pandas2.3.3-pydantic1.10.11 42.70% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.11-pandas2.3.3-pydantic2.12.3 42.86% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.11-pandas3.0.0-pydantic1.10.11 42.71% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.11-pandas3.0.0-pydantic2.12.3 42.87% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.12-pandas2.3.3-pydantic1.10.11 42.69% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.12-pandas2.3.3-pydantic2.12.3 42.84% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.12-pandas3.0.0-pydantic1.10.11 42.70% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.12-pandas3.0.0-pydantic2.12.3 42.86% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.13-pandas2.3.3-pydantic1.10.11 42.69% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.13-pandas2.3.3-pydantic2.12.3 42.84% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.13-pandas3.0.0-pydantic1.10.11 42.70% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.13-pandas3.0.0-pydantic2.12.3 42.86% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.14-pandas2.3.3-pydantic2.12.3 42.83% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-Windows-py3.14-pandas3.0.0-pydantic2.12.3 42.84% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.10-pandas2.3.3-pydantic1.10.11 42.72% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.10-pandas2.3.3-pydantic2.12.3 42.88% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.11-pandas2.3.3-pydantic1.10.11 42.72% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.11-pandas2.3.3-pydantic2.12.3 42.88% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.11-pandas3.0.0-pydantic1.10.11 42.74% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.11-pandas3.0.0-pydantic2.12.3 42.89% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.12-pandas2.3.3-pydantic1.10.11 42.71% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.12-pandas2.3.3-pydantic2.12.3 42.87% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.12-pandas3.0.0-pydantic1.10.11 42.72% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.12-pandas3.0.0-pydantic2.12.3 42.88% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.13-pandas2.3.3-pydantic1.10.11 42.71% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.13-pandas2.3.3-pydantic2.12.3 42.87% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.13-pandas3.0.0-pydantic1.10.11 42.72% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.13-pandas3.0.0-pydantic2.12.3 42.88% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.14-pandas2.3.3-pydantic2.12.3 42.85% <100.00%> (+0.14%) ⬆️
unit-tests-pandas-macOS-py3.14-pandas3.0.0-pydantic2.12.3 42.86% <100.00%> (+0.14%) ⬆️
unit-tests-polars-Linux-py3.10-pandas2.3.3-polars1.33.1 31.02% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Linux-py3.11-pandas2.3.3-polars1.33.1 31.01% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Linux-py3.12-pandas2.3.3-polars1.33.1 31.01% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Linux-py3.13-pandas2.3.3-polars1.33.1 31.01% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Linux-py3.14-pandas2.3.3-polars1.33.1 30.88% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Windows-py3.10-pandas2.3.3-polars1.33.1 30.95% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Windows-py3.11-pandas2.3.3-polars1.33.1 30.93% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Windows-py3.12-pandas2.3.3-polars1.33.1 30.93% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Windows-py3.13-pandas2.3.3-polars1.33.1 30.93% <40.00%> (-0.07%) ⬇️
unit-tests-polars-Windows-py3.14-pandas2.3.3-polars1.33.1 30.81% <40.00%> (-0.07%) ⬇️
unit-tests-polars-macOS-py3.10-pandas2.3.3-polars1.33.1 30.96% <40.00%> (-0.07%) ⬇️
unit-tests-polars-macOS-py3.11-pandas2.3.3-polars1.33.1 30.95% <40.00%> (-0.07%) ⬇️
unit-tests-pyspark-Linux-py3.11-pandas2.3.3-polars 39.00% <80.00%> (?)
unit-tests-strategies-Linux-py3.13-pandas2.3.3-pydantic2.12.3 34.17% <80.00%> (-0.02%) ⬇️
unit-tests-strategies-Linux-py3.14-pandas3.0.0-pydantic2.12.3 34.11% <80.00%> (?)
unit-tests-strategies-Windows-py3.11-pandas2.3.3-pydantic2.12.3 34.09% <80.00%> (-0.01%) ⬇️
unit-tests-xarray-Linux-py3.10-pandas2.3.3-polars 31.74% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Linux-py3.11-pandas2.3.3-polars 31.74% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Linux-py3.12-pandas2.3.3-polars 31.74% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Linux-py3.13-pandas2.3.3-polars 31.74% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Linux-py3.14-pandas2.3.3-polars 31.67% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Windows-py3.10-pandas2.3.3-polars 31.67% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Windows-py3.11-pandas2.3.3-polars 31.67% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Windows-py3.12-pandas2.3.3-polars 31.67% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Windows-py3.13-pandas2.3.3-polars 31.67% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-Windows-py3.14-pandas2.3.3-polars 31.60% <40.00%> (-0.06%) ⬇️
unit-tests-xarray-macOS-py3.10-pandas2.3.3-polars 31.68% <40.00%> (-0.06%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

cosmicbboy and others added 3 commits July 2, 2026 13:10
Reverting the runtime wrap in DataFrameModel.validate() to cast() so
validate returns the original backend object (plain pandas, dask, etc.)
while keeping the DataFrame[Self] return annotation for static typing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove GitHub issue number references from test modules, docstrings, and
docs introduced on this branch:

- Rename mypy/pyright test modules named after issue 1487 to describe
  what they cover (schema retention, check_types output, column access,
  typed wrapper).
- Drop the issue link from the codegen module docstring and the docs
  seealso block (which also used invalid rST syntax in MyST markdown).
- Remove redundant pandas_dataframe_getitem_hook_test.py fixture module,
  a subset of pandas_dataframe_getitem.py whose *_test.py suffix caused
  pytest to collect it as a test file.
- Remove unused TYPE_CHECKING import and a placeholder-less f-string.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generate-and-paste workflow of
pandera.typing.codegen.generate_typed_dataframe_source made for janky
developer experience. Remove the codegen module, its unit tests, the
typed_wrapper pyright test module, and the docs section recommending it.

The mypy plugin improvements (column __getitem__/attribute inference)
and the generic DataFrame parameterization fixes are unchanged; the
remaining pyright test modules cover the plugin-free behavior that
still works under Pyright/Pylance. The docs now state plainly that
column-level inference is a mypy-plugin feature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type hints are not retained

1 participant