Skip to content

fix(pandas): apply parsers to Index fields and schema components - #2435

Merged
cosmicBboy merged 1 commit into
unionai-oss:mainfrom
cycsmail:bugfix-1684-index-parsers
Aug 4, 2026
Merged

fix(pandas): apply parsers to Index fields and schema components#2435
cosmicBboy merged 1 commit into
unionai-oss:mainfrom
cycsmail:bugfix-1684-index-parsers

Conversation

@cycsmail

@cycsmail cycsmail commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #1684

Problem

@pa.parser on a field annotated as Index is silently ignored:

import pandas as pd
import pandera.pandas as pa
from pandera.typing import Index, Series

class Model(pa.DataFrameModel):
    idx: Index[int]
    col: Series[int]

    @pa.parser("idx")
    @classmethod
    def double(cls, series):
        return series * 2

df = pd.DataFrame({"col": [1, 2, 3]}, index=pd.Index([1, 2, 3]))
print(Model.validate(df).index.tolist())  # [1, 2, 3], parser never ran

Two things cause this. FieldInfo.index_properties never forwards the field's parsers, so Index components built from a DataFrameModel don't have them at all. And even with parsers passed directly to pa.Index, the index backend validates a series copy of the index and discards the parsed result, while coercion runs before parsers, so a parser that cleans raw values into a coercible form never gets a chance (the same ordering issue #2047 fixed for columns).

This forwards parsers in FieldInfo.index_properties, writes the validated series back to check_obj.index in IndexBackend when the schema has parsers, and defers index coercion (index-level and dataframe-level) to the array backend when parsers are present, so they run before dtype coercion like they do for columns. Added regression tests in tests/pandas/test_parsers.py for the model Index field case from the issue, parsers on a pa.Index inside a DataFrameSchema, and an index parser whose output needs coercion.

Signed-off-by: Sebastian Cao <cycsmail@gmail.com>
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.39%. Comparing base (85cc2a1) to head (02098dc).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2435   +/-   ##
=======================================
  Coverage   91.39%   91.39%           
=======================================
  Files         193      193           
  Lines       17569    17571    +2     
=======================================
+ Hits        16057    16059    +2     
  Misses       1512     1512           
Flag Coverage Δ
unit-tests-base-Linux-py3.10 16.49% <0.00%> (-0.01%) ⬇️
unit-tests-base-Linux-py3.11 16.49% <0.00%> (-0.01%) ⬇️
unit-tests-base-Linux-py3.12 16.49% <0.00%> (-0.01%) ⬇️
unit-tests-base-Linux-py3.13 16.49% <0.00%> (-0.01%) ⬇️
unit-tests-base-Linux-py3.14 16.36% <0.00%> (-0.01%) ⬇️
unit-tests-base-Windows-py3.10 16.41% <0.00%> (-0.01%) ⬇️
unit-tests-base-Windows-py3.11 16.41% <0.00%> (-0.01%) ⬇️
unit-tests-base-Windows-py3.12 16.41% <0.00%> (-0.01%) ⬇️
unit-tests-base-Windows-py3.13 16.41% <0.00%> (-0.01%) ⬇️
unit-tests-base-Windows-py3.14 16.29% <0.00%> (-0.01%) ⬇️
unit-tests-base-macOS-py3.10 16.43% <0.00%> (-0.01%) ⬇️
unit-tests-base-macOS-py3.11 16.43% <0.00%> (-0.01%) ⬇️
unit-tests-base-macOS-py3.12 16.43% <0.00%> (-0.01%) ⬇️
unit-tests-base-macOS-py3.13 16.43% <0.00%> (-0.01%) ⬇️
unit-tests-base-macOS-py3.14 16.30% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Linux-py3.10-pandas2.3.3-polars 23.66% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Linux-py3.11-pandas2.3.3-polars 23.66% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Linux-py3.12-pandas2.3.3-polars 23.66% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Linux-py3.13-pandas2.3.3-polars 23.66% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Linux-py3.14-pandas2.3.3-polars 23.57% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Windows-py3.10-pandas2.3.3-polars 23.57% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Windows-py3.11-pandas2.3.3-polars 23.57% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Windows-py3.12-pandas2.3.3-polars 23.57% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Windows-py3.13-pandas2.3.3-polars 23.57% <0.00%> (-0.01%) ⬇️
unit-tests-dask-Windows-py3.14-pandas2.3.3-polars 23.49% <0.00%> (-0.01%) ⬇️
unit-tests-dask-macOS-py3.10-pandas2.3.3-polars 23.60% <0.00%> (-0.01%) ⬇️
unit-tests-dask-macOS-py3.11-pandas2.3.3-polars 23.60% <0.00%> (-0.01%) ⬇️
unit-tests-fastapi-Linux-py3.10-pandas2.3.3-pydantic2.12.3 24.08% <25.00%> (-0.01%) ⬇️
unit-tests-fastapi-Linux-py3.12-pandas2.3.3-pydantic2.12.3 24.08% <25.00%> (-0.01%) ⬇️
unit-tests-fastapi-Linux-py3.13-pandas3.0.0-pydantic2.12.3 24.15% <25.00%> (-0.01%) ⬇️
unit-tests-fastapi-Linux-py3.14-pandas2.3.3-pydantic2.12.3 24.00% <25.00%> (-0.63%) ⬇️
unit-tests-geopandas-Linux-py3.11-pandas2.3.3-pydantic2.12.3 29.32% <75.00%> (-0.77%) ⬇️
unit-tests-geopandas-Linux-py3.11-pandas3.0.0-pydantic2.12.3 29.32% <75.00%> (-0.77%) ⬇️
unit-tests-geopandas-Linux-py3.12-pandas2.3.3-pydantic2.12.3 29.32% <75.00%> (-0.81%) ⬇️
unit-tests-geopandas-Windows-py3.10-pandas2.3.3-pydantic2.12.3 29.23% <75.00%> (-0.77%) ⬇️
unit-tests-ibis-Linux-py3.10-pandas2.3.3-polars 28.10% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Linux-py3.11-pandas2.3.3-polars 28.10% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Linux-py3.12-pandas2.3.3-polars 28.10% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Linux-py3.13-pandas2.3.3-polars 28.10% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Linux-py3.14-pandas2.3.3-polars 27.92% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Windows-py3.10-pandas2.3.3-polars 28.03% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Windows-py3.11-pandas2.3.3-polars 28.03% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Windows-py3.12-pandas2.3.3-polars 28.03% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Windows-py3.13-pandas2.3.3-polars 28.03% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-Windows-py3.14-pandas2.3.3-polars 27.84% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-macOS-py3.10-pandas2.3.3-polars 28.04% <0.00%> (-0.01%) ⬇️
unit-tests-ibis-macOS-py3.11-pandas2.3.3-polars 28.04% <0.00%> (-0.01%) ⬇️
unit-tests-io-Linux-py3.11-pandas3.0.0-pydantic2.12.3 37.16% <25.00%> (-1.02%) ⬇️
unit-tests-io-Linux-py3.12-pandas3.0.0-pydantic2.12.3 37.16% <25.00%> (-0.01%) ⬇️
unit-tests-io-Linux-py3.13-pandas3.0.0-pydantic2.12.3 37.16% <25.00%> (?)
unit-tests-modin-dask-macOS-py3.10-pandas2.3.3-polars 29.87% <75.00%> (-0.82%) ⬇️
unit-tests-modin-dask-macOS-py3.11-pandas2.3.3-polars 29.87% <75.00%> (-0.79%) ⬇️
unit-tests-modin-ray-Linux-py3.10-pandas2.3.3-polars 29.91% <75.00%> (+<0.01%) ⬆️
unit-tests-modin-ray-Linux-py3.11-pandas2.3.3-polars 29.91% <75.00%> (+<0.01%) ⬆️
unit-tests-modin-ray-Linux-py3.12-pandas2.3.3-polars 29.91% <75.00%> (+<0.01%) ⬆️
unit-tests-modin-ray-macOS-py3.10-pandas2.3.3-polars 29.85% <75.00%> (+<0.01%) ⬆️
unit-tests-modin-ray-macOS-py3.11-pandas2.3.3-polars 29.85% <75.00%> (+<0.01%) ⬆️
unit-tests-mypy-Linux-py3.10-pandas2.3.3-pydantic2.12.3 22.88% <0.00%> (-0.62%) ⬇️
unit-tests-narwhals-Linux-py3.10 40.39% <50.00%> (+<0.01%) ⬆️
unit-tests-narwhals-Linux-py3.11 40.38% <50.00%> (+<0.01%) ⬆️
unit-tests-narwhals-Linux-py3.12 40.38% <50.00%> (+<0.01%) ⬆️
unit-tests-narwhals-Linux-py3.13 40.38% <50.00%> (+<0.01%) ⬆️
unit-tests-narwhals-Linux-py3.14 40.22% <50.00%> (+<0.01%) ⬆️
unit-tests-narwhals-backend-ibis-py3.10 31.61% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-ibis-py3.11 31.50% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-ibis-py3.12 31.50% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-ibis-py3.13 31.50% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-ibis-py3.14 31.30% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-polars-py3.10 36.08% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-polars-py3.11 36.07% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-polars-py3.12 36.07% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-polars-py3.13 36.07% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-polars-py3.14 35.94% <0.00%> (-0.01%) ⬇️
unit-tests-narwhals-backend-pyspark-py3.10 43.26% <75.00%> (+<0.01%) ⬆️
unit-tests-narwhals-backend-pyspark-py3.11 43.29% <75.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.10-pandas2.3.3-pydantic1.10.11 41.71% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.10-pandas2.3.3-pydantic2.12.3 41.86% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.11-pandas2.3.3-pydantic1.10.11 41.71% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.11-pandas2.3.3-pydantic2.12.3 41.86% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.11-pandas3.0.0-pydantic1.10.11 41.72% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.11-pandas3.0.0-pydantic2.12.3 41.87% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.12-pandas2.3.3-pydantic1.10.11 41.69% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.12-pandas2.3.3-pydantic2.12.3 41.85% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.12-pandas3.0.0-pydantic1.10.11 41.71% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.12-pandas3.0.0-pydantic2.12.3 41.86% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.13-pandas2.3.3-pydantic1.10.11 41.69% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.13-pandas2.3.3-pydantic2.12.3 41.85% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.13-pandas3.0.0-pydantic1.10.11 41.71% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.13-pandas3.0.0-pydantic2.12.3 41.86% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.14-pandas2.3.3-pydantic2.12.3 41.83% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Linux-py3.14-pandas3.0.0-pydantic2.12.3 41.84% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.10-pandas2.3.3-pydantic1.10.11 41.62% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.10-pandas2.3.3-pydantic2.12.3 41.77% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.11-pandas2.3.3-pydantic1.10.11 41.62% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.11-pandas2.3.3-pydantic2.12.3 41.77% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.11-pandas3.0.0-pydantic1.10.11 41.63% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.11-pandas3.0.0-pydantic2.12.3 41.79% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.12-pandas2.3.3-pydantic1.10.11 41.61% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.12-pandas2.3.3-pydantic2.12.3 41.76% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.12-pandas3.0.0-pydantic1.10.11 41.62% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.12-pandas3.0.0-pydantic2.12.3 41.77% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.13-pandas2.3.3-pydantic1.10.11 41.61% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.13-pandas2.3.3-pydantic2.12.3 41.76% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.13-pandas3.0.0-pydantic2.12.3 41.77% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.14-pandas2.3.3-pydantic2.12.3 41.74% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-Windows-py3.14-pandas3.0.0-pydantic2.12.3 41.75% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.10-pandas2.3.3-pydantic1.10.11 41.65% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.10-pandas2.3.3-pydantic2.12.3 41.80% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.11-pandas2.3.3-pydantic1.10.11 41.65% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.11-pandas2.3.3-pydantic2.12.3 41.80% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.11-pandas3.0.0-pydantic1.10.11 41.66% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.11-pandas3.0.0-pydantic2.12.3 41.81% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.12-pandas2.3.3-pydantic1.10.11 41.63% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.12-pandas2.3.3-pydantic2.12.3 41.79% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.12-pandas3.0.0-pydantic1.10.11 41.65% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.12-pandas3.0.0-pydantic2.12.3 41.80% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.13-pandas2.3.3-pydantic1.10.11 41.63% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.13-pandas2.3.3-pydantic2.12.3 41.79% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.13-pandas3.0.0-pydantic1.10.11 41.65% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.13-pandas3.0.0-pydantic2.12.3 41.80% <100.00%> (+<0.01%) ⬆️
unit-tests-pandas-macOS-py3.14-pandas3.0.0-pydantic2.12.3 41.78% <100.00%> (+<0.01%) ⬆️
unit-tests-polars-Linux-py3.10-pandas2.3.3-polars1.33.1 30.27% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Linux-py3.11-pandas2.3.3-polars1.33.1 30.26% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Linux-py3.12-pandas2.3.3-polars1.33.1 30.26% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Linux-py3.13-pandas2.3.3-polars1.33.1 30.26% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Linux-py3.14-pandas2.3.3-polars1.33.1 30.14% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Windows-py3.10-pandas2.3.3-polars1.33.1 30.20% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Windows-py3.11-pandas2.3.3-polars1.33.1 30.19% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Windows-py3.12-pandas2.3.3-polars1.33.1 30.19% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Windows-py3.13-pandas2.3.3-polars1.33.1 30.19% <0.00%> (-0.01%) ⬇️
unit-tests-polars-Windows-py3.14-pandas2.3.3-polars1.33.1 30.06% <0.00%> (-0.01%) ⬇️
unit-tests-polars-macOS-py3.10-pandas2.3.3-polars1.33.1 30.21% <0.00%> (-0.01%) ⬇️
unit-tests-polars-macOS-py3.11-pandas2.3.3-polars1.33.1 30.20% <0.00%> (-0.01%) ⬇️
unit-tests-strategies-Linux-py3.11-pandas3.0.0-pydantic2.12.3 33.37% <50.00%> (-0.88%) ⬇️
unit-tests-strategies-Linux-py3.13-pandas2.3.3-pydantic2.12.3 33.37% <50.00%> (-0.88%) ⬇️
unit-tests-strategies-Linux-py3.14-pandas2.3.3-pydantic2.12.3 33.32% <50.00%> (-0.87%) ⬇️
unit-tests-strategies-Linux-py3.14-pandas3.0.0-pydantic2.12.3 33.31% <50.00%> (-0.85%) ⬇️
unit-tests-xarray-Linux-py3.10-pandas2.3.3-polars 30.98% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Linux-py3.11-pandas2.3.3-polars 30.98% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Linux-py3.12-pandas2.3.3-polars 30.98% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Linux-py3.13-pandas2.3.3-polars 30.98% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Linux-py3.14-pandas2.3.3-polars 30.91% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Windows-py3.10-pandas2.3.3-polars 30.91% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Windows-py3.11-pandas2.3.3-polars 30.91% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Windows-py3.12-pandas2.3.3-polars 30.91% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Windows-py3.13-pandas2.3.3-polars 30.91% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-Windows-py3.14-pandas2.3.3-polars 30.84% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-macOS-py3.10-pandas2.3.3-polars 30.92% <0.00%> (-0.01%) ⬇️
unit-tests-xarray-macOS-py3.14-pandas2.3.3-polars 30.85% <0.00%> (?)

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
cosmicBboy merged commit 106a663 into unionai-oss:main Aug 4, 2026
325 checks passed
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.

@pa.parser does not work for Index fields

2 participants