Skip to content

Conversation

@amerberg
Copy link
Contributor

@amerberg amerberg commented Jan 2, 2026

#2158 changed how pandera fetches annotations in the DataFrameModel.__init_subclass__ method, which can lead to some surprising behavior. For instance, consider the following snippet:

import pandera.pandas as pa
from pandera import dtypes
from pandera.api.base.model import MetaModel


_ = MetaModel.__annotations__


class Base(pa.DataFrameModel):
    x: dtypes.String = pa.Field(alias="x_alias")


class Child(Base):
    pass


print("Base columns:", Base.to_schema().columns.keys())
print("Child columns:", Child.to_schema().columns.keys())

As of 0.27.1, the output is

Base columns: dict_keys(['x_alias'])
Child columns: dict_keys(['x'])

This behavior appears to be related to an issue noted in PEP 749, namely that accessing __annotations__ on a metaclass before accessing __annotations__ on a child class causes annotations to leak from the parent class to the child class. In this case, that leakage results in pandera.api.dataframe.model.DataFrameModel.__init_subclass__ adding a new field on the child class, but without the alias (or any other properties) that are set on the parent.

Per documentation:

You should avoid accessing annotations directly on any object. Instead, use annotationlib.get_annotations() (Python 3.14+) or inspect.get_annotations() (Python 3.10+).

Since pandera has dropped support for Python 3.9, the fix here is just to use inspect.get_annotations (which is an alias for annotationlib.get_annotations in Python 3.14). I'm not quite sure what the rationale for the change in #2158 to check for annotations in __annotations_cache__, but if astral-sh/ruff#17859 is to believed that might also be unsafe.

The snippet above is perhaps contrived in that it involves accessing the metaclass's annotations directly for no good reason, but we are hitting real bugs that are preventing us from upgrading beyond 0.26.1, and the changes here resolve them for us.

@codecov
Copy link

codecov bot commented Jan 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.86%. Comparing base (f5c55cb) to head (909098f).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2196      +/-   ##
==========================================
+ Coverage   81.51%   83.86%   +2.34%     
==========================================
  Files         137      137              
  Lines       10898    10607     -291     
==========================================
+ Hits         8884     8896      +12     
+ Misses       2014     1711     -303     

☔ View full report in Codecov by Sentry.
📢 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.

@amerberg amerberg force-pushed the init_subclass_annotations branch from 71fbc16 to 790a428 Compare January 2, 2026 03:05
@amerberg amerberg marked this pull request as ready for review January 2, 2026 03:44
Copy link
Collaborator

@deepyaman deepyaman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

@deepyaman deepyaman changed the title use get_annotations instead of direct __annotations__ access Use get_annotations instead of direct __annotations__ access Jan 5, 2026
@deepyaman deepyaman merged commit 8abbf26 into unionai-oss:main Jan 5, 2026
224 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.

2 participants