This is an automated email from the ASF dual-hosted git repository. hope pushed a commit to branch release-1.4 in repository https://gitbox.apache.org/repos/asf/paimon.git
commit 7d195662515a9228213a491ce4ad327c944f58bf 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
