Skip to content

GH-39914: [pyarrow] Fall back to schema field type for unparseable complex dtype metadata - #51167

Open
tritsystem wants to merge 1 commit into
apache:mainfrom
tritsystem:fix-pandas-compat-complex-type-fallback
Open

GH-39914: [pyarrow] Fall back to schema field type for unparseable complex dtype metadata#51167
tritsystem wants to merge 1 commit into
apache:mainfrom
tritsystem:fix-pandas-compat-complex-type-fallback

Conversation

@tritsystem

Copy link
Copy Markdown

Rationale for this change

pd.read_parquet() (and Table.to_pandas() generally, with no
types_mapper/dtype_backend="pyarrow") raises TypeError for any
column whose pandas metadata records a complex ArrowDtype -- list,
struct, or dictionary -- as its numpy_type, e.g.
"list<item: string>[pyarrow]". This has been broken since the metadata
round-tripping was introduced, and is the long-standing root cause of
pandas-dev/pandas#53011 (open since 2023, still reproducing today with
no workaround at the pandas level).

importpandasaspdimportpyarrowaspadata=pd.DataFrame({
"a": pd.Series([["x"], ["x", "y"]], dtype=pd.ArrowDtype(pa.list_(pa.string()))),
})
data.to_parquet("data.parquet") # SUCCESSpd.read_parquet("data.parquet") # TypeError: data type 'list<item: string>[pyarrow]' not understood

#39914 (this PR's linked issue) previously fixed the case where an
explicit types_mapper / dtype_backend="pyarrow" is passed at read
time -- #44720 resolved those columns earlier in _get_extension_dtypes,
so the buggy string-parsing branch is skipped entirely for that path.
The default backend, and any metadata-only path with no
types_mapper, still hit the original crash -- which is why the pandas
issue has stayed open and users keep re-discovering it (see the thread's
comments through 2025-08).

What changes are included in this PR?

In _get_extension_dtypes(), when pandas_dtype(dtype) raises
TypeError on the metadata's numpy_type string, fall back to building
the ArrowDtype directly from the schema's actual field type
(table.schema.field(name).type) instead of propagating the error. The
schema always carries the precise type regardless of whether the
metadata string happens to be parseable, and this is exactly the pattern
several commenters on the pandas issue independently proposed (e.g.
this comment)
but that was never implemented in a merged fix.

Are these changes tested?

Added test_to_pandas_extension_dtypes_mapping_complex_type_no_types_mapper,
covering both a list and a struct ArrowDtype column round-tripped
through Table.to_pandas() with notypes_mapper -- the exact case
that previously crashed. Confirmed red on unpatched code / green after
the fix.

Also manually verified (not just the new test) against pandas 3.0.4 +
pyarrow 24.0.0:

  • list, struct, and dictionary ArrowDtype columns round-trip through
    both the default backend and dtype_backend="pyarrow".
  • A double write/read/write/read round-trip of a struct column (the
    scenario from this comment)
    still works.
  • Plain numpy dtypes and simple extension dtypes (Int64) are
    unaffected -- no regression to the already-working paths.

Are there any user-facing changes?

This PR contains a "Critical Fix".
This makes pd.read_parquet(...) (default backend, no
dtype_backend="pyarrow" needed) work for any DataFrame containing a
complex ArrowDtype column (list/struct/dictionary), which currently
raises an opaque TypeError and has no workaround short of stripping the
pandas metadata before writing.

AI Generation Disclosure

Generated with Claude Code. The bug
was found by searching for confirmed, still-open, high-severity issues
across large open-source projects; root-caused and fixed by tracing the
actual pandas_compat.py source against the linked pandas issue and the
prior partial fix (#44720), then verified against a fresh install of the
latest pandas/pyarrow rather than assumed from the issue thread alone.

🤖 Generated with Claude Code

…le complex dtype metadata
pd.read_parquet() (and Table.to_pandas() generally) raises TypeError when
a column's pandas metadata records a complex ArrowDtype (list, struct,
dictionary, ...) as its 'numpy_type', e.g.
"list<item: string>[pyarrow]". _get_extension_dtypes() passes this string
straight to pandas.api.types.pandas_dtype(), which cannot parse nested
Arrow type syntax and raises TypeError -- so any DataFrame that has such
a column can never round-trip through parquet with the default (numpy)
backend, and this has stayed broken since 2023.
apacheGH-39914 previously fixed the case where an explicit types_mapper /
dtype_backend="pyarrow" is passed, by resolving those columns earlier so
this code path is skipped entirely. The default backend, and any
metadata-only path with no types_mapper, still hit the crash.
Fix: when pandas_dtype(dtype) raises TypeError, fall back to building the
ArrowDtype directly from the schema's actual field type instead of
giving up. The schema always has the precise type available regardless
of whether the metadata string can be parsed back.
Verified against pandas 3.0.4 + pyarrow 24.0.0: list, struct, and
dictionary ArrowDtype columns now round-trip through to_pandas() and
parquet with no types_mapper/dtype_backend needed, a double
write/read/write/read round-trip of a struct column still works, and
plain numpy dtypes plus simple extension dtypes (Int64) are unaffected.
Fixespandas-dev/pandas#53011
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #39914has been automatically assigned in GitHub to PR creator.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tritsystem