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

lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 37b5cb9863 [python] Support rename branch api (#7561)
37b5cb9863 is described below

commit 37b5cb98636bc4291d6eca00cc173c44faa80ef1
Author: xuzifu666 <[email protected]>
AuthorDate: Tue Mar 31 16:27:42 2026 +0800

    [python] Support rename branch api (#7561)
---
 docs/content/pypaimon/python-api.md                | 13 ++++++
 paimon-python/pypaimon/branch/branch_manager.py    | 13 ++++++
 .../pypaimon/branch/catalog_branch_manager.py      | 16 +++++++
 .../pypaimon/branch/filesystem_branch_manager.py   | 54 ++++++++++++++++++++++
 paimon-python/pypaimon/table/file_store_table.py   | 15 ++++++
 .../pypaimon/tests/table/file_store_table_test.py  | 46 ++++++++++++++++++
 6 files changed, 157 insertions(+)

diff --git a/docs/content/pypaimon/python-api.md 
b/docs/content/pypaimon/python-api.md
index 7cdd86d731..197a018ef1 100644
--- a/docs/content/pypaimon/python-api.md
+++ b/docs/content/pypaimon/python-api.md
@@ -915,6 +915,19 @@ Delete an existing branch:
 table.branch_manager().drop_branch('feature_branch')
 ```
 
+### Rename Branch
+
+Rename an existing branch to a new name:
+
+```python
+# Rename a branch
+table.branch_manager().rename_branch('old_branch_name', 'new_branch_name')
+```
+
+{{< hint warning >}}
+The source branch must exist and cannot be the main branch. The target branch 
name must be valid and not already exist.
+{{< /hint >}}
+
 ### Fast Forward
 
 Fast forward the main branch to a specific branch:
diff --git a/paimon-python/pypaimon/branch/branch_manager.py 
b/paimon-python/pypaimon/branch/branch_manager.py
index cb2b1e87ad..fbd5fede38 100644
--- a/paimon-python/pypaimon/branch/branch_manager.py
+++ b/paimon-python/pypaimon/branch/branch_manager.py
@@ -65,6 +65,19 @@ class BranchManager:
         """
         raise NotImplementedError("Subclasses must implement drop_branch")
 
+    def rename_branch(self, from_branch: str, to_branch: str) -> None:
+        """
+        Rename a branch.
+        
+        Args:
+            from_branch: Current name of the branch
+            to_branch: New name for the branch
+        
+        Raises:
+            NotImplementedError: Subclasses must implement this method
+        """
+        raise NotImplementedError("Subclasses must implement rename_branch")
+
     def fast_forward(self, branch_name: str) -> None:
         """
         Fast forward the current branch to the specified branch.
diff --git a/paimon-python/pypaimon/branch/catalog_branch_manager.py 
b/paimon-python/pypaimon/branch/catalog_branch_manager.py
index a70c6ace99..1cffc60f4e 100644
--- a/paimon-python/pypaimon/branch/catalog_branch_manager.py
+++ b/paimon-python/pypaimon/branch/catalog_branch_manager.py
@@ -121,6 +121,22 @@ class CatalogBranchManager(BranchManager):
 
         self._execute_post(_drop)
 
+    def rename_branch(self, from_branch: str, to_branch: str) -> None:
+        """
+        Rename a branch.
+
+        Args:
+            from_branch: Current name of the branch
+            to_branch: New name for the branch
+
+        Raises:
+            ValueError: If from_branch or to_branch is invalid
+        """
+        def _rename(catalog: Catalog):
+            catalog.rename_branch(self.identifier, from_branch, to_branch)
+
+        self._execute_post(_rename)
+
     def fast_forward(self, branch_name: str) -> None:
         """
         Fast forward the current branch to the specified branch.
diff --git a/paimon-python/pypaimon/branch/filesystem_branch_manager.py 
b/paimon-python/pypaimon/branch/filesystem_branch_manager.py
index e5c2982c39..ca8111b62f 100644
--- a/paimon-python/pypaimon/branch/filesystem_branch_manager.py
+++ b/paimon-python/pypaimon/branch/filesystem_branch_manager.py
@@ -199,6 +199,60 @@ class FileSystemBranchManager(BranchManager):
             )
             raise RuntimeError(f"Failed to delete branch '{branch_name}'") 
from e
 
+    def rename_branch(self, from_branch: str, to_branch: str) -> None:
+        """
+        Rename a branch.
+
+        Args:
+            from_branch: Current name of the branch
+            to_branch: New name for the branch
+
+        Raises:
+            ValueError: If from_branch or to_branch is blank, from_branch 
doesn't exist,
+                       to_branch already exists, or trying to rename the main 
branch
+            RuntimeError: If the rename operation fails
+        """
+        # Check if from_branch is main branch
+        if self.is_main_branch(from_branch):
+            raise ValueError(f"Cannot rename the main branch '{from_branch}'.")
+
+        # Check if from_branch is blank
+        if not from_branch or from_branch.isspace():
+            raise ValueError("Source branch name shouldn't be blank.")
+
+        # Check if to_branch is blank
+        if not to_branch or to_branch.isspace():
+            raise ValueError("Target branch name shouldn't be blank.")
+
+        # Validate the new branch name
+        try:
+            self.validate_branch(to_branch)
+        except ValueError as e:
+            raise ValueError(f"Invalid target branch name: {e}")
+
+        # Check if from_branch exists
+        if not self.branch_exists(from_branch):
+            raise ValueError(f"Source branch '{from_branch}' doesn't exist.")
+
+        # Check if to_branch already exists
+        if self.branch_exists(to_branch):
+            raise ValueError(f"Target branch '{to_branch}' already exists.")
+
+        try:
+            # Rename the branch directory
+            from_path = self.branch_path(from_branch)
+            to_path = self.branch_path(to_branch)
+            self.file_io.rename(from_path, to_path)
+            logger.info(f"Successfully renamed branch from '{from_branch}' to 
'{to_branch}'")
+        except Exception as e:
+            logger.warning(
+                f"Renaming branch from '{from_branch}' to '{to_branch}' failed 
due to an exception. "
+                f"Please try again."
+            )
+            raise RuntimeError(
+                f"Failed to rename branch from '{from_branch}' to 
'{to_branch}'"
+            ) from e
+
     def fast_forward(self, branch_name: str) -> None:
         """
         Fast forward the current branch to the specified branch.
diff --git a/paimon-python/pypaimon/table/file_store_table.py 
b/paimon-python/pypaimon/table/file_store_table.py
index d725d48021..9115c8e885 100644
--- a/paimon-python/pypaimon/table/file_store_table.py
+++ b/paimon-python/pypaimon/table/file_store_table.py
@@ -132,6 +132,21 @@ class FileStoreTable(Table):
         from pypaimon.changelog.changelog_manager import ChangelogManager
         return ChangelogManager(self.file_io, self.table_path, 
self.current_branch())
 
+    def rename_branch(self, from_branch: str, to_branch: str) -> None:
+        """
+        Rename a branch.
+
+        Args:
+            from_branch: Current name of the branch
+            to_branch: New name for the branch
+
+        Raises:
+            ValueError: If from_branch or to_branch is blank, from_branch 
doesn't exist,
+                       or to_branch already exists
+        """
+        branch_mgr = self.branch_manager()
+        branch_mgr.rename_branch(from_branch, to_branch)
+
     def create_tag(
             self,
             tag_name: str,
diff --git a/paimon-python/pypaimon/tests/table/file_store_table_test.py 
b/paimon-python/pypaimon/tests/table/file_store_table_test.py
index 72c1ee7e2c..22068d3a01 100644
--- a/paimon-python/pypaimon/tests/table/file_store_table_test.py
+++ b/paimon-python/pypaimon/tests/table/file_store_table_test.py
@@ -339,6 +339,52 @@ class FileStoreTableTest(unittest.TestCase):
         self.assertEqual(copied_table.identifier, self.table.identifier)
         self.assertEqual(copied_table.table_path, self.table.table_path)
 
+    def test_rename_branch_basic(self):
+        """Test rename_branch method."""
+        # Get branch_manager
+        branch_manager = self.table.branch_manager()
+
+        # Create a branch first
+        branch_manager.create_branch("old-branch")
+        self.assertTrue(branch_manager.branch_exists("old-branch"))
+
+        # Rename the branch using table's rename_branch method
+        self.table.rename_branch("old-branch", "new-branch")
+
+        # Verify old branch doesn't exist
+        self.assertFalse(branch_manager.branch_exists("old-branch"))
+
+        # Verify new branch exists
+        self.assertTrue(branch_manager.branch_exists("new-branch"))
+
+    def test_rename_branch_from_nonexistent(self):
+        """Test renaming from non-existent branch raises error."""
+        with self.assertRaises(ValueError) as context:
+            self.table.rename_branch("nonexistent-branch", "new-branch")
+
+        self.assertIn("doesn't exist", str(context.exception))
+
+    def test_rename_branch_to_existing(self):
+        """Test renaming to existing branch raises error."""
+        # Get branch_manager
+        branch_manager = self.table.branch_manager()
+
+        # Create two branches
+        branch_manager.create_branch("branch1")
+        branch_manager.create_branch("branch2")
+
+        with self.assertRaises(ValueError) as context:
+            self.table.rename_branch("branch1", "branch2")
+
+        self.assertIn("already exists", str(context.exception))
+
+    def test_rename_main_branch_fails(self):
+        """Test renaming main branch raises error."""
+        with self.assertRaises(ValueError) as context:
+            self.table.rename_branch("main", "new-branch")
+
+        self.assertIn("main branch", str(context.exception))
+
     def test_comment_none(self):
         """Test that comment() returns None when table has no comment."""
         # Default table created without comment should return None

Reply via email to