Goal
Add support for reading and writing tables using Iceberg's encryption model, interoperable with other engines.
Deliver this incrementally in PyIceberg. Reuse Rust implementations through pyiceberg-core when the necessary bindings are available, without making complete Rust-backed readers and writers a prerequisite.
This tracks Iceberg table encryption, not object-store server-side encryption such as S3 SSE-KMS.
Approach
- Keep Python's public APIs, catalog commits, and provider integrations.
- Define narrow interfaces so individual Python implementations can later delegate to Rust.
- Keep key management and encrypted I/O reusable across manifests, manifest lists, and data files.
- Deliver encrypted reads before requiring the full encrypted-write path.
1. Models, configuration, and key-metadata codec
Rust prerequisite: merge and release apache/iceberg-rust#3206, then establish the required pyiceberg-core version or capability check. This step does not need file I/O, KMS, or manifest bindings.
2. Key management
Depends on: step 1.
Rust option: bind EncryptionManager and add a Python KMS provider adapter. Rust can own Iceberg's key-management mechanics while Python keeps cloud-provider integration. Define callback, concurrency, and error behavior before switching implementations.
3. Encrypted streams and file access
Depends on: steps 1 and 2.
Rust option: expose Rust's encrypted input/output through a storage adapter. Python's seekable streams must be adapted to Rust's asynchronous range-read/write interfaces. Complete native manifest or data-file bindings are not required.
4. Data files and delete files
Depends on: steps 1 and 2; encrypted-stream consumers also need step 3. Parquet integration can proceed alongside step 3 because Parquet uses its own encryption mechanism.
Rust option: reuse the codec and encryption manager while retaining PyArrow. Replacing entire data-file readers/writers is separate work requiring native bindings, Arrow batch transfer, and equivalent schema, writer-option, and output-metadata behavior.
5. Manifests and manifest lists
Depends on: steps 1-3. Read support can land independently; encrypted V3 writes also require the writer/version support tracked in step 6.
Rust option: Python readers/writers can use Rust-backed encrypted streams first. Moving complete operations into Rust additionally requires complete manifest and manifest-list bindings, version support, metadata conversion, and stable Python errors. Today's byte-parsing helpers are not that full integration.
6. Table writes, snapshots, and commits
Depends on: the relevant file integrations from steps 4 and 5. Start V3 write prerequisites in parallel with the earlier steps.
Boundary: keep catalog transactions in Python. Rust-backed operations must return the file information and key entries Python needs to commit; completing a file write is not a table commit.
Completion criteria
Add focused coverage with each step, rather than leaving all integration work until the end.
Optional follow-up: complete native readers and writers
Full Rust-backed manifest and data-file I/O can follow independently. Where Rust performs the next operation, keep decoded keys and encryption state native rather than round-tripping through Python.
This is not a prerequisite for shipping Python encryption support.
Related work
Goal
Add support for reading and writing tables using Iceberg's encryption model, interoperable with other engines.
Deliver this incrementally in PyIceberg. Reuse Rust implementations through
pyiceberg-corewhen the necessary bindings are available, without making complete Rust-backed readers and writers a prerequisite.This tracks Iceberg table encryption, not object-store server-side encryption such as S3 SSE-KMS.
Approach
1. Models, configuration, and key-metadata codec
StandardKeyMetadata: apache/iceberg-python#3948.Rust prerequisite: merge and release apache/iceberg-rust#3206, then establish the required
pyiceberg-coreversion or capability check. This step does not need file I/O, KMS, or manifest bindings.2. Key management
Depends on: step 1.
Rust option: bind
EncryptionManagerand add a Python KMS provider adapter. Rust can own Iceberg's key-management mechanics while Python keeps cloud-provider integration. Define callback, concurrency, and error behavior before switching implementations.3. Encrypted streams and file access
Depends on: steps 1 and 2.
Rust option: expose Rust's encrypted input/output through a storage adapter. Python's seekable streams must be adapted to Rust's asynchronous range-read/write interfaces. Complete native manifest or data-file bindings are not required.
4. Data files and delete files
DataFilerecords, including newly rolled output files.Depends on: steps 1 and 2; encrypted-stream consumers also need step 3. Parquet integration can proceed alongside step 3 because Parquet uses its own encryption mechanism.
Rust option: reuse the codec and encryption manager while retaining PyArrow. Replacing entire data-file readers/writers is separate work requiring native bindings, Arrow batch transfer, and equivalent schema, writer-option, and output-metadata behavior.
5. Manifests and manifest lists
Depends on: steps 1-3. Read support can land independently; encrypted V3 writes also require the writer/version support tracked in step 6.
Rust option: Python readers/writers can use Rust-backed encrypted streams first. Moving complete operations into Rust additionally requires complete manifest and manifest-list bindings, version support, metadata conversion, and stable Python errors. Today's byte-parsing helpers are not that full integration.
6. Table writes, snapshots, and commits
Depends on: the relevant file integrations from steps 4 and 5. Start V3 write prerequisites in parallel with the earlier steps.
Boundary: keep catalog transactions in Python. Rust-backed operations must return the file information and key entries Python needs to commit; completing a file write is not a table commit.
Completion criteria
Add focused coverage with each step, rather than leaving all integration work until the end.
Optional follow-up: complete native readers and writers
Full Rust-backed manifest and data-file I/O can follow independently. Where Rust performs the next operation, keep decoded keys and encryption state native rather than round-tripping through Python.
This is not a prerequisite for shipping Python encryption support.
Related work