From 979509798f134d320f63ef1acf2aece757dd2525 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 7 Oct 2025 17:54:42 +0000 Subject: [PATCH] chore(librarian): add ability to update version files for proto-only libraries --- .generator/cli.py | 21 ++++++++++--- .generator/test_cli.py | 71 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/.generator/cli.py b/.generator/cli.py index d332ac1a7e35..550f2e3f6056 100644 --- a/.generator/cli.py +++ b/.generator/cli.py @@ -1058,7 +1058,10 @@ def _process_version_file(content, version, version_path) -> str: Returns: A string with the modified content. """ - pattern = r"(__version__\s*=\s*[\"'])([^\"']+)([\"'].*)" + if version_path.name.endswith("gapic_version.py"): + pattern = r"(__version__\s*=\s*[\"'])([^\"']+)([\"'].*)" + else: + pattern = r"(version\s*=\s*[\"'])([^\"']+)([\"'].*)" replacement_string = f"\\g<1>{version}\\g<3>" new_content, num_replacements = re.subn(pattern, replacement_string, content) if num_replacements == 0: @@ -1071,8 +1074,9 @@ def _process_version_file(content, version, version_path) -> str: def _update_version_for_library( repo: str, output: str, path_to_library: str, version: str ): - """Updates the version string in `**/gapic_version.py` and `samples/**/snippet_metadata.json` - for a given library. + """Updates the version string in `**/gapic_version.py`, `setup.py`, + `pyproject.toml` and `samples/**/snippet_metadata.json` for a + given library, if applicable. Args: repo(str): This directory will contain all directories that make up a @@ -1088,8 +1092,15 @@ def _update_version_for_library( """ # Find and update gapic_version.py files - gapic_version_files = Path(f"{repo}/{path_to_library}").rglob("**/gapic_version.py") - for version_file in gapic_version_files: + version_files = Path(f"{repo}/{path_to_library}").rglob("**/gapic_version.py") + if not version_files: + # Fallback to `pyproject.toml`` or `setup.py``. Proto-only libraries have + # version information in `setup.py` or `pyproject.toml` instead of `gapic_version.py`. + pyproject_toml = Path(f"{repo}/{path_to_library}/pyproject.toml") + setup_py = Path(f"{repo}/{path_to_library}/setup.py") + version_files = [pyproject_toml if pyproject_toml.exists() else setup_py] + + for version_file in version_files: updated_content = _process_version_file( _read_text_file(version_file), version, version_file ) diff --git a/.generator/test_cli.py b/.generator/test_cli.py index cd5d11bb7e21..ee5963a033fe 100644 --- a/.generator/test_cli.py +++ b/.generator/test_cli.py @@ -965,7 +965,7 @@ def test_update_global_changelog(mocker, mock_release_init_request_file): handle.write.assert_called_once_with("[google-cloud-language==1.2.3]") -def test_update_version_for_library_success(mocker): +def test_update_version_for_library_success_gapic(mocker): m = mock_open() mock_rglob = mocker.patch( @@ -984,7 +984,67 @@ def test_update_version_for_library_success(mocker): handle = m() assert handle.write.call_args_list[0].args[0] == '__version__ = "1.2.3"' + # Get all the arguments passed to the mock's write method + # and join them into a single string. + written_content = "".join( + [call.args[0] for call in handle.write.call_args_list[1:]] + ) + # Create the expected output string with the correct formatting. + assert ( + written_content + == '{\n "clientLibrary": {\n "version": "1.2.3"\n }\n}\n' + ) + + +def test_update_version_for_library_success_proto_only_setup_py(mocker): + m = mock_open() + + mock_rglob = mocker.patch("pathlib.Path.rglob") + mock_rglob.side_effect = [[], [pathlib.Path("repo/setup.py")]] + mock_shutil_copy = mocker.patch("shutil.copy") + mock_content = 'version = "1.2.2"' + mock_json_metadata = {"clientLibrary": {"version": "0.1.0"}} + + with unittest.mock.patch("cli.open", m): + mocker.patch("cli._read_text_file", return_value=mock_content) + mocker.patch("cli._read_json_file", return_value=mock_json_metadata) + _update_version_for_library( + "repo", "output", "packages/google-cloud-language", "1.2.3" + ) + + handle = m() + assert handle.write.call_args_list[0].args[0] == 'version = "1.2.3"' + # Get all the arguments passed to the mock's write method + # and join them into a single string. + written_content = "".join( + [call.args[0] for call in handle.write.call_args_list[1:]] + ) + # Create the expected output string with the correct formatting. + assert ( + written_content + == '{\n "clientLibrary": {\n "version": "1.2.3"\n }\n}\n' + ) + + +def test_update_version_for_library_success_proto_only_py_project_toml(mocker): + m = mock_open() + mock_path_exists = mocker.patch("pathlib.Path.exists") + mock_rglob = mocker.patch("pathlib.Path.rglob") + mock_rglob.side_effect = [[], [pathlib.Path("repo/pyproject.toml")]] + mock_shutil_copy = mocker.patch("shutil.copy") + mock_content = 'version = "1.2.2"' + mock_json_metadata = {"clientLibrary": {"version": "0.1.0"}} + + with unittest.mock.patch("cli.open", m): + mocker.patch("cli._read_text_file", return_value=mock_content) + mocker.patch("cli._read_json_file", return_value=mock_json_metadata) + _update_version_for_library( + "repo", "output", "packages/google-cloud-language", "1.2.3" + ) + + handle = m() + assert handle.write.call_args_list[0].args[0] == 'version = "1.2.3"' # Get all the arguments passed to the mock's write method # and join them into a single string. written_content = "".join( @@ -1099,18 +1159,18 @@ def test_update_changelog_for_library_failure(mocker): def test_process_version_file_success(): - version_file_contents = '__version__ = "1.2.2"' + version_file_contents = 'version = "1.2.2"' new_version = "1.2.3" modified_content = _process_version_file( - version_file_contents, new_version, "file.txt" + version_file_contents, new_version, Path("file.txt") ) - assert modified_content == f'__version__ = "{new_version}"' + assert modified_content == f'version = "{new_version}"' def test_process_version_file_failure(): """Tests that value error is raised if the version string cannot be found""" with pytest.raises(ValueError): - _process_version_file("", "", "") + _process_version_file("", "", Path("")) def test_create_main_version_header(): @@ -1367,6 +1427,7 @@ def test_get_staging_child_directory_gapic_versioned(): expected = "v1" assert _get_staging_child_directory(api_path, False) == expected + def test_get_staging_child_directory_gapic_non_versioned(): """ Tests the behavior for GAPIC clients with no standard 'v' prefix versioning.