Skip to content

Commit 55fd696

Browse files
authored
chore: add ability to update the global changelog via release-init command (googleapis#14349)
This PR builds on top of googleapis#14327 and adds the ability to update the client library version in the root [CHANGELOG.md](https://github.com/googleapis/google-cloud-python/blob/main/CHANGELOG.md) Towards googleapis/librarian#886
1 parent 6dac6af commit 55fd696

File tree

2 files changed

+113
-5
lines changed

2 files changed

+113
-5
lines changed

.generator/cli.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,32 @@
4747
SOURCE_DIR = "source"
4848

4949

50+
def _read_text_file(path: str) -> str:
51+
"""Helper function that reads a text file path and returns the content.
52+
53+
Args:
54+
path(str): The file path to read.
55+
56+
Returns:
57+
str: The contents of the file.
58+
"""
59+
60+
with open(path, "r") as f:
61+
return f.read()
62+
63+
64+
def _write_text_file(path: str, updated_content: str):
65+
"""Helper function that writes a text file path with the given content.
66+
67+
Args:
68+
path(str): The file path to write.
69+
updated_content(str): The contents to write to the file.
70+
"""
71+
72+
with open(path, "w") as f:
73+
f.write(updated_content)
74+
75+
5076
def _read_json_file(path: str) -> Dict:
5177
"""Helper function that reads a json file path and returns the loaded json content.
5278
@@ -449,6 +475,32 @@ def _get_libraries_to_prepare_for_release(library_entries: Dict) -> List[dict]:
449475
]
450476

451477

478+
def _update_global_changelog(changelog_src: str, changelog_dest: str, all_libraries: List[dict]):
479+
"""Updates the versions of libraries in the main CHANGELOG.md.
480+
481+
Args:
482+
changelog_src(str): Path to the changelog file to read.
483+
changelog_dest(str): Path to the changelog file to write.
484+
all_libraries(Dict): Dictionary containing all of the library versions to
485+
modify.
486+
"""
487+
488+
def replace_version_in_changelog(content):
489+
new_content = content
490+
for library in all_libraries:
491+
package_name = library["id"]
492+
version = library["version"]
493+
# Find the entry for the given package in the format`<package name>==<version>`
494+
# Replace the `<version>` part of the string.
495+
pattern = re.compile(f"(\\[{re.escape(package_name)})(==)([\\d\\.]+)(\\])")
496+
replacement = f"\\g<1>=={version}\\g<4>"
497+
new_content = pattern.sub(replacement, new_content)
498+
return new_content
499+
500+
updated_content = replace_version_in_changelog(_read_text_file(changelog_src))
501+
_write_text_file(changelog_dest, updated_content)
502+
503+
452504
def handle_release_init(
453505
librarian: str = LIBRARIAN_DIR, repo: str = REPO_DIR, output: str = OUTPUT_DIR
454506
):
@@ -478,15 +530,18 @@ def handle_release_init(
478530
request_data
479531
)
480532

481-
# TODO(https://github.com/googleapis/google-cloud-python/pull/14349):
482-
# Update library global changelog file.
533+
_update_global_changelog(
534+
f"{repo}/CHANGELOG.md",
535+
f"{output}/CHANGELOG.md",
536+
libraries_to_prep_for_release,
537+
)
483538

484539
# Prepare the release for each library by updating the
485540
# library specific version files and library specific changelog.
486541
for library_release_data in libraries_to_prep_for_release:
487542
# TODO(https://github.com/googleapis/google-cloud-python/pull/14350):
488543
# Update library specific version files.
489-
# TODO(https://github.com/googleapis/google-cloud-python/pull/14351):
544+
# TODO(https://github.com/googleapis/google-cloud-python/pull/14353):
490545
# Conditionally update the library specific CHANGELOG if there is a change.
491546
pass
492547

.generator/test_cli.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import logging
1717
import os
1818
import subprocess
19+
import unittest.mock
1920
from unittest.mock import MagicMock, mock_open
2021

2122
import pytest
@@ -34,9 +35,12 @@
3435
_get_libraries_to_prepare_for_release,
3536
_locate_and_extract_artifact,
3637
_read_json_file,
38+
_read_text_file,
3739
_run_individual_session,
3840
_run_nox_sessions,
3941
_run_post_processor,
42+
_update_global_changelog,
43+
_write_text_file,
4044
handle_build,
4145
handle_configure,
4246
handle_generate,
@@ -461,7 +465,7 @@ def test_read_valid_json(mocker):
461465
assert result == {"key": "value"}
462466

463467

464-
def test_file_not_found(mocker):
468+
def test_json_file_not_found(mocker):
465469
"""Tests behavior when the file does not exist."""
466470
mocker.patch("builtins.open", side_effect=FileNotFoundError("No such file"))
467471

@@ -505,10 +509,11 @@ def test_get_libraries_to_prepare_for_release(mock_release_init_request_file):
505509
assert libraries_to_prep_for_release[0]["release_triggered"]
506510

507511

508-
def test_handle_release_init_success(mock_release_init_request_file):
512+
def test_handle_release_init_success(mocker, mock_release_init_request_file):
509513
"""
510514
Simply tests that `handle_release_init` runs without errors.
511515
"""
516+
mocker.patch("cli._update_global_changelog", return_value=None)
512517
handle_release_init()
513518

514519

@@ -518,3 +523,51 @@ def test_handle_release_init_fail():
518523
"""
519524
with pytest.raises(ValueError):
520525
handle_release_init()
526+
527+
528+
def test_read_valid_text_file(mocker):
529+
"""Tests reading a valid text file."""
530+
mock_content = "some text"
531+
mocker.patch("builtins.open", mocker.mock_open(read_data=mock_content))
532+
result = _read_text_file("fake/path.txt")
533+
assert result == "some text"
534+
535+
536+
def test_text_file_not_found(mocker):
537+
"""Tests behavior when the file does not exist."""
538+
mocker.patch("builtins.open", side_effect=FileNotFoundError("No such file"))
539+
540+
with pytest.raises(FileNotFoundError):
541+
_read_text_file("non/existent/path.text")
542+
543+
544+
def test_write_text_file():
545+
"""Tests writing a text file.
546+
See https://docs.python.org/3/library/unittest.mock.html#mock-open
547+
"""
548+
m = mock_open()
549+
550+
with unittest.mock.patch("cli.open", m):
551+
_write_text_file("fake_path.txt", "modified content")
552+
553+
handle = m()
554+
handle.write.assert_called_once_with("modified content")
555+
556+
557+
def test_update_global_changelog(mocker, mock_release_init_request_file):
558+
"""Tests that the global changelog is updated
559+
with the new version for a given library.
560+
See https://docs.python.org/3/library/unittest.mock.html#mock-open
561+
"""
562+
m = mock_open()
563+
request_data = _read_json_file(f"{LIBRARIAN_DIR}/{RELEASE_INIT_REQUEST_FILE}")
564+
libraries = _get_libraries_to_prepare_for_release(request_data)
565+
566+
with unittest.mock.patch("cli.open", m):
567+
mocker.patch(
568+
"cli._read_text_file", return_value="[google-cloud-language==1.2.2]"
569+
)
570+
_update_global_changelog("source", "output", libraries)
571+
572+
handle = m()
573+
handle.write.assert_called_once_with("[google-cloud-language==1.2.3]")

0 commit comments

Comments
 (0)