IO: Consolidate PyArrow logic into io/pyarrow.py before decomposition #3812
Description
Summary
Before decomposing pyiceberg/io/pyarrow.py into focused submodules (#3737, #3738), we should consolidate PyArrow-specific logic that currently lives outside the module. This ensures all PyArrow calls route through a single boundary, making the subsequent split clean and enabling future engine substitution.
Motivation
Per discussion in #3737, @rambleraptor noted that the first useful step is ensuring no PyArrow logic occurs outside pyarrow.py. Currently several modules import pyarrow directly and implement compute logic inline rather than delegating through pyiceberg.io.pyarrow.
When we later introduce a ComputeEngine protocol, any PyArrow logic outside the module boundary bypasses the protocol and prevents clean substitution.
Audit
Grepped pyiceberg/ (excluding io/pyarrow.py and tests) for runtime import pyarrow statements (both top-level and inline). Excluded TYPE_CHECKING-only imports since those have no runtime dependency.
| Location | What it does | Action |
|---|---|---|
table/upsert_util.py |
PyArrow table joins, group_by, compute, cast, take | Absorb |
table/inspect.py |
Builds pa.schema + pa.Table.from_pylist for metadata inspection | TBD |
transforms.py |
pyarrow_transform() dispatch on pa.Array/ChunkedArray |
TBD |
table/__init__.py |
Entry points accept pa.Table, delegate to io.pyarrow | Leave |
table/deletion_vector.py |
Single pa.chunked_array() call | Leave |
catalog/__init__.py |
Delegates to io.pyarrow for schema conversion | Leave |
Plan
One PR per absorption. Each is a pure refactor: move code into io/pyarrow.py, have the caller import from pyiceberg.io.pyarrow instead of pyarrow directly. No behavior change, all existing tests pass unchanged.
- PR A: Absorb
table/upsert_util.pyPyArrow logic - PR B:
table/inspect.py(pending discussion) - PR C:
transforms.py(pending discussion)
Related
- Decompose io/pyarrow.py into focused modules to enable pluggable compute engines #3737 - Decompose io/pyarrow.py into focused modules
- IO: Extract PyArrowFileIO into dedicated module #3738 - Extract PyArrowFileIO (first decomposition step)
- Pluggable Backend Interface with DataFusion for Bounded-Memory Compute #3715 / Pluggable Backend Interface with DataFusion for Bounded-Memory Compute #3716 - Previous pluggable backend attempt (rejected as too large)
Activity
qzyu999 commentedon Aug 19, 2026
Notes on
inspect.pyandtransforms.pyAdding some analysis on the two "TBD" items to guide discussion.
table/inspect.pyEvery method in
InspectTablefollows the same pattern: iterate over metadata objects, build a list of Python dicts, define apa.schema(...), and callpa.Table.from_pylist(data, schema=schema).Absorption would mean moving the schema definitions and
from_pylistcalls into helper functions inio/pyarrow.py:Then
inspect.pywould only gather dicts and delegate:My read: This is technically doable but adds ~10 trivial wrapper functions whose only job is
pa.Table.from_pylist(data, schema=X). There's no compute here that benefits from engine substitution. A DataFusion engine would not build metadata inspection tables any differently. This is output formatting, not a substitution targetLeaning toward leave, but open to feedback.
transforms.pyEach transform subclass defines a
pyarrow_transform()method returning aCallable[[pa.Array], pa.Array]. The shared_pyiceberg_transform_wrapperdispatches onpa.Arrayvspa.ChunkedArray.Absorption would mean moving the implementations out of the classes and into a dispatch function in
io/pyarrow.py:My read: This fights the existing OO design. The
pyarrow_transform()methods are polymorphic for a reason: each transform subclass owns its own implementation. Pulling them into a centralisinstancedispatch:datafusion_transform()method or a generictransform(engine)dispatch alongside the existing methods, not replace them.The methods are already scoped by name (
pyarrow_transform) to signal they're the PyArrow-specific path. They are the extension point.Leaning toward leave.
Summary
inspect.pyfrom_pylistintoio/pyarrow.pyhelperstransforms.pyHappy to reconsider if there's a use case I'm missing.
IO: Absorb upsert PyArrow logic into io/pyarrow.py
IO: Absorb upsert PyArrow logic into io/pyarrow.py