This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new c270543f [python] Preserve external batch commit identity (#916)
c270543f is described below

commit c270543f20570772b91ce5804ffe39e6aa34a4ac
Author: Jingsong Lee <[email protected]>
AuthorDate: Tue Sep 22 23:05:54 2026 +0800

    [python] Preserve external batch commit identity (#916)
---
 .../python/python/pypaimon_rust/datafusion.pyi     |  3 ++
 bindings/python/src/write.rs                       | 14 +++++++++
 bindings/python/tests/test_table_commit.py         | 33 +++++++++++++++++++++-
 3 files changed, 49 insertions(+), 1 deletion(-)

diff --git a/bindings/python/python/pypaimon_rust/datafusion.pyi 
b/bindings/python/python/pypaimon_rust/datafusion.pyi
index b3ebed81..8c40c14d 100644
--- a/bindings/python/python/pypaimon_rust/datafusion.pyi
+++ b/bindings/python/python/pypaimon_rust/datafusion.pyi
@@ -252,6 +252,9 @@ class StreamTableCommit:
     def abort(self, messages: Sequence[CommitMessage]) -> None: ...
 
 class BatchWriteBuilder:
+    def _with_commit_user(self, commit_user: str) -> "BatchWriteBuilder":
+        """Internal PyPaimon bridge for commits produced by an external Python 
writer."""
+        ...
     def with_overwrite(self, static_partition: Optional[Dict[str, Any]] = {}) 
-> "BatchWriteBuilder":
         """Configure both writer and committer. Explicit None restores append.
 
diff --git a/bindings/python/src/write.rs b/bindings/python/src/write.rs
index 4caa28a5..258676e6 100644
--- a/bindings/python/src/write.rs
+++ b/bindings/python/src/write.rs
@@ -189,6 +189,20 @@ impl PyBatchWriteBuilder {
 
 #[pymethods]
 impl PyBatchWriteBuilder {
+    /// Internal PyPaimon bridge: retain the identity of the external Python 
writer.
+    fn _with_commit_user(
+        mut slf: PyRefMut<'_, Self>,
+        commit_user: String,
+    ) -> PyResult<PyRefMut<'_, Self>> {
+        slf.context
+            .table
+            .new_write_builder()
+            .with_commit_user(commit_user.clone())
+            .map_err(to_py_err)?;
+        slf.context.commit_user = commit_user;
+        Ok(slf)
+    }
+
     /// No argument enables overwrite with an empty spec; explicit None 
restores append.
     #[pyo3(signature = (static_partition=Some(HashMap::new())))]
     fn with_overwrite<'py>(
diff --git a/bindings/python/tests/test_table_commit.py 
b/bindings/python/tests/test_table_commit.py
index 12b6f666..2af7a926 100644
--- a/bindings/python/tests/test_table_commit.py
+++ b/bindings/python/tests/test_table_commit.py
@@ -91,7 +91,8 @@ def test_public_api_separates_batch_and_stream(tmp_path):
         assert not hasattr(obj, "deserialize_commit_message")
     assert not hasattr(batch.new_commit(), "filter_and_commit")
     assert not hasattr(batch.new_commit(), "overwrite")
-    assert not hasattr(stream.new_commit(), "truncate_table")
+    for method in ("overwrite", "_overwrite", "truncate_table"):
+        assert not hasattr(stream.new_commit(), method)
     with pytest.raises(TypeError):
         batch.new_commit().commit([], commit_identifier=1)
     with pytest.raises(TypeError):
@@ -154,6 +155,36 @@ def 
test_deserialized_batch_commit_uses_target_builder(tmp_path):
     assert _rows(table) == [1]
 
 
[email protected]("overwrite", [False, True])
[email protected]("serialized", [False, True])
+def test_batch_bridge_preserves_external_commit_user(tmp_path, overwrite, 
serialized):
+    table = _table(tmp_path, primary_key=True, options={"bucket": "1"})
+    _append(table, [1], [10])
+    builder = table.new_batch_write_builder()
+    assert builder._with_commit_user("python-batch-job") is builder
+    if overwrite:
+        builder.with_overwrite()
+    messages = _prepare(builder, [2], [20])
+    if serialized:
+        messages = _roundtrip(messages)
+    builder.new_commit().commit(messages)
+    assert _rows(table) == ([2] if overwrite else [1, 2])
+    snapshot = _snapshot(table)
+    assert snapshot["commitUser"] == "python-batch-job"
+    assert snapshot["commitIdentifier"] == 2**63 - 1
+
+
[email protected]("user", ["", "../job", "a/b"])
+def test_invalid_batch_bridge_commit_user_preserves_identity(tmp_path, user):
+    table = _table(tmp_path)
+    builder = 
table.new_batch_write_builder()._with_commit_user("python-batch-job")
+    with pytest.raises(ValueError):
+        builder._with_commit_user(user)
+    builder.new_commit().commit(_prepare(builder, [1], [10]))
+    assert _snapshot(table)["commitUser"] == "python-batch-job"
+    assert _rows(table) == [1]
+
+
 def test_abort_serialized_messages_deletes_files(tmp_path):
     table = _table(tmp_path)
     builder = table.new_batch_write_builder()

Reply via email to