Skip to content

GH-50312: [Python] Fix UUID extension type round-trip to pandas returning bytes - #50325

Merged
rok merged 7 commits into
apache:mainfrom
parker-cassar:gh-50312-uuid-pandas-roundtrip
Aug 18, 2026
Merged

GH-50312: [Python] Fix UUID extension type round-trip to pandas returning bytes#50325
rok merged 7 commits into
apache:mainfrom
parker-cassar:gh-50312-uuid-pandas-roundtrip

Conversation

@parker-cassar

@parker-cassarparker-cassar commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

Converting a Table with an arrow.uuid extension column to pandas currently produces a column of bytes instead of uuid.UUID objects. Without a dedicated conversion path, Table.to_pandas() / Array.to_pandas() falls back to the storage type (fixed_size_binary(16)) and materializes raw bytes. Downstream callers (and tests such as those landing via pandas UUID Parquet coverage) expect uuid.UUID objects, matching what to_pylist() / UuidScalar.as_py() already return.

Note: the original issue suggested this might be specific to Python 3.14, but the same bytes result reproduces on Python 3.10-3.14 with current PyArrow - UuidType never had a pandas conversion path.

Closes#50312

What changes are included in this PR?

Convert UUID extension arrays to uuid.UUID objects in the C++ pandas conversion path (integrated from @rok's proposal in rok#55), rather than relying on a Python to_pandas_dtype() / __from_arrow__ compat layer:

  • Construct uuid.UUID objects from storage bytes in helpers.cc (UuidFromBytes) and use that from arrow_to_pandas.cc
  • Reuse empty kwargs / shared empty tuple constants so conversion does not allocate per element
  • Keep to_numpy(zero_copy_only=False) on UUID extension arrays returning storage bytes (extension to NumPy should follow storage type)
  • Add/extend regression tests for pandas round-trip and NumPy behavior

Are these changes tested?

Yes.

Acceptance criteria:

  • Tests added for the UUID to pandas round-trip (and NumPy storage behavior)
  • Relevant local / CI Python tests passing for the touched paths
  • Follows project style / review feedback (lint fix included)
  • No intentional breaking public API change beyond correcting incorrect bytes return values to uuid.UUID for to_pandas()

Before / after evidence (backend; console):

# Before (repro on Python 3.10-3.14 with unfixed PyArrow)
>>> type(result_df.loc[0, "id"])
<class 'bytes'>
# After (with this PR)
>>> type(result_df.loc[0, "id"])
<class 'uuid.UUID'>

Also covered by added unit tests in python/pyarrow/tests/parquet/test_data_types.py and python/pyarrow/tests/test_extension_type.py.

Are there any user-facing changes?

Yes. Table.to_pandas() / Array.to_pandas() now returns uuid.UUID for arrow.uuid columns instead of bytes. to_numpy(zero_copy_only=False) continues to expose storage bytes.

@github-actions

Copy link
Copy Markdown

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

@parker-cassar
parker-cassar marked this pull request as draft July 2, 2026 03:44
@parker-cassar
parker-cassar marked this pull request as ready for review July 2, 2026 08:04
@parker-cassar
parker-cassarforce-pushed the gh-50312-uuid-pandas-roundtrip branch from 5b15a46 to 386b1b6CompareJuly 2, 2026 08:14
@GiTaDi-CrEaTe

Copy link
Copy Markdown

Hey @parker-cassar, thanks so much for jumping on this so quickly. Just out of curiosity—since this delegates to to_pylist(), do you think this pattern might end up being useful for other Arrow extension types down the way???

@rokrok left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for working on this @parker-cassar!
A couple of questions since we're aiming to support Pandas 3.x and Pandas 2.x too for a while.

Comment threadpython/pyarrow/tests/parquet/test_data_types.py
Comment threadpython/pyarrow/types.pxi Outdated
@parker-cassar
parker-cassarforce-pushed the gh-50312-uuid-pandas-roundtrip branch 2 times, most recently from ea0d9f2 to b7f87d1CompareJuly 8, 2026 04:04
@parker-cassar

Copy link
Copy Markdown
ContributorAuthor

Hey @parker-cassar, thanks so much for jumping on this so quickly. Just out of curiosity—since this delegates to to_pylist(), do you think this pattern might end up being useful for other Arrow extension types down the way???

Maybe! It works here because the target is object dtype anyway, so building the Python objects is unavoidable. Other extension types could use the same from_arrow + reshape structure, but anything performance-sensitive would want a zero-copy path instead of going through Python objects.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Jul 8, 2026
@parker-cassar
parker-cassarforce-pushed the gh-50312-uuid-pandas-roundtrip branch 3 times, most recently from bc83f11 to 457ff20CompareJuly 8, 2026 07:15
@parker-cassar

parker-cassar commented Jul 16, 2026

Copy link
Copy Markdown
ContributorAuthor

@rok Applied both of your suggestions. Ready for another look whenever you have time.

@rok

rok commented Jul 21, 2026

Copy link
Copy Markdown
Member

@parker-cassar thank you for your patience :)

I've been thinking about this a bit and perhaps we could convert to UUID in c++ and avoid awkward Python compat layer? Pandas had a UUID type proposal that seems stalled but could get completed. See my proposal what this could look like. Let me ask for some feedback from others before you continue.
@pitrou@jorisvandenbossche@raulcd@AlenkaF

@rok

rok commented Jul 21, 2026

Copy link
Copy Markdown
Member

@parker-cassar I think we better implement this in C++ kind of like proposed here. This will likely improve performance and give us a cleaner API/make compat layer with pandas neater. Sorry for not suggesting this sooner. Do you think you can continue in this vein?

@parker-cassar

Copy link
Copy Markdown
ContributorAuthor

@parker-cassar I think we better implement this in C++ kind of like proposed here. This will likely improve performance and give us a cleaner API/make compat layer with pandas neater. Sorry for not suggesting this sooner. Do you think you can continue in this vein?

Yes I agree and am happy you suggested this (: the Python compat layer approach doesn't really make sense here, especially since a UUID column in pandas isn't exactly a rare case. Doing it natively is cleaner. I'll give it a go and I'll try to have a PR in a few days or less based on your proposal.

@parker-cassar

Copy link
Copy Markdown
ContributorAuthor

@rok Quick update: your proposal is integrated and the full round-trip passes. I'm still working on my last pass and I will have it up in the next day or two. Thanks for the proposal, made it a lot easier.

@parker-cassar
parker-cassarforce-pushed the gh-50312-uuid-pandas-roundtrip branch from 457ff20 to 10eab91CompareJuly 29, 2026 07:41
@parker-cassar
parker-cassarforce-pushed the gh-50312-uuid-pandas-roundtrip branch from 10eab91 to b8c8338CompareJuly 29, 2026 07:47
Comment threadpython/pyarrow/src/arrow/python/arrow_to_pandas.cc Outdated

@rokrok left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have a potential performance improvement suggestion, but other than that this looks pretty good.

Comment threadpython/pyarrow/src/arrow/python/helpers.cc Outdated
@parker-cassar
parker-cassarforce-pushed the gh-50312-uuid-pandas-roundtrip branch from eaf0c4b to 2f35759CompareAugust 9, 2026 23:52

@rokrok left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we're almost there - just the matter of ensuring type(uuid_array.to_numpy(zero_copy_only=False)[0]) is bytes, since extension-array NumPy conversion should delegate to its storage type.

Comment threadpython/pyarrow/src/arrow/python/arrow_to_pandas.cc Outdated
Comment threadpython/pyarrow/tests/test_extension_type.py
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting committer review Awaiting committer review labels Aug 10, 2026
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Aug 10, 2026
@parker-cassar

Copy link
Copy Markdown
ContributorAuthor

@rok The macOS 15-intel check failed in the Install MinIO step (wget couldn't resolve dl.min.io) before anything was built or tested. Are you able to re-run that job?

rok
rok approved these changes Aug 10, 2026

@rokrok left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. I'll wait a bit if @AlenkaF or @pitrou have time to review.

@github-actionsgithub-actionsBot added awaiting merge Awaiting merge and removed awaiting change review Awaiting change review labels Aug 11, 2026

@AlenkaFAlenkaF left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for working on this @parker-cassar and providing the C++ version of the solution. I think the PR looks good and am happy to see it get merged!

@parker-cassar

parker-cassar commented Aug 11, 2026

Copy link
Copy Markdown
ContributorAuthor

Thanks @AlenkaF! Big thanks to @rok too!

@parker-cassar

Copy link
Copy Markdown
ContributorAuthor

@rok@AlenkaF just wondering if there's anything left on my end before this can be merged? Thanks!

rok
rok approved these changes Aug 18, 2026

@rokrok left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry for the wait @parker-cassar and thank you for your flexibility and patience!
I think this is ready and will merge.

@rok
rok merged commit 892c73d into apache:mainAug 18, 2026
49 of 50 checks passed
@rokrok removed the awaiting merge Awaiting merge label Aug 18, 2026
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 3 benchmarking runs that have been run so far on merge-commit 892c73d.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 1 possible false positive for unstable benchmarks that are known to sometimes produce them.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Python][Parquet] FIXED_LEN_BYTE_ARRAY fails to cast to UUID on Python 3.14 / Nightly builds

5 participants

@parker-cassar@GiTaDi-CrEaTe@rok@pitrou@AlenkaF