Skip to content

Commit 8ac6820

Browse files
authored
chore: verify library distribution name (#14441)
Fixes googleapis/librarian#1946 🦕
1 parent 7db18a2 commit 8ac6820

File tree

5 files changed

+74
-1
lines changed

5 files changed

+74
-1
lines changed

.generator/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ RUN git clone --depth 1 https://github.com/googleapis/synthtool.git /tmp/synthto
172172
bazel_env/bin/python3.9 -m pip install /tmp/synthtool nox && \
173173
rm -rf /tmp/synthtool
174174

175+
# Install build which is used to get the metadata of package config files.
176+
COPY .generator/requirements.in .
177+
RUN python3.9 -m pip install -r requirements.in
178+
175179
# Copy the CLI script into the container.
176180
COPY .generator/cli.py .
177181
RUN chmod a+rx ./cli.py

.generator/cli.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
from pathlib import Path
2828
from typing import Dict, List
2929

30+
import build.util
31+
3032
try:
3133
import synthtool
3234
from synthtool.languages import python_mono_repo
@@ -546,12 +548,54 @@ def _verify_library_namespace(library_id: str, repo: str):
546548
)
547549

548550

551+
def _get_library_dist_name(library_id: str, repo: str) -> str:
552+
"""
553+
Gets the package name by programmatically building the metadata.
554+
555+
Args:
556+
library_id: id of the library.
557+
repo: This directory will contain all directories that make up a
558+
library, the .librarian folder, and any global file declared in
559+
the config.yaml.
560+
561+
Returns:
562+
str: The library name string if found, otherwise None.
563+
"""
564+
library_path = f"{repo}/packages/{library_id}"
565+
metadata = build.util.project_wheel_metadata(library_path)
566+
return metadata.get("name")
567+
568+
569+
def _verify_library_dist_name(library_id: str, repo: str):
570+
"""Verifies the library distribution name against its config files.
571+
572+
This function ensures that:
573+
1. At least one of `setup.py` or `pyproject.toml` exists and is valid.
574+
2. Any existing config file's 'name' property matches the `library_id`.
575+
576+
Args:
577+
library_id: id of the library.
578+
repo: This directory will contain all directories that make up a
579+
library, the .librarian folder, and any global file declared in
580+
the config.yaml.
581+
582+
Raises:
583+
ValueError: If a name in an existing config file does not match the `library_id`.
584+
"""
585+
dist_name = _get_library_dist_name(library_id, repo)
586+
if dist_name != library_id:
587+
raise ValueError(
588+
f"The distribution name `{dist_name}` does not match the folder `{library_id}`."
589+
)
590+
591+
549592
def handle_build(librarian: str = LIBRARIAN_DIR, repo: str = REPO_DIR):
550593
"""The main coordinator for validating client library generation."""
551594
try:
552595
request_data = _read_json_file(f"{librarian}/{BUILD_REQUEST_FILE}")
553596
library_id = _get_library_id(request_data)
554597
_verify_library_namespace(library_id, repo)
598+
_verify_library_dist_name(library_id, repo)
555599
_run_nox_sessions(library_id, repo)
556600
except Exception as e:
557601
raise ValueError("Build failed.") from e

.generator/requirements-test.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ pytest-cov
1717
pytest-mock
1818
gcp-synthtool @ git+https://github.com/googleapis/synthtool@5aa438a342707842d11fbbb302c6277fbf9e4655
1919
starlark-pyo3>=2025.1
20+
build

.generator/requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build

.generator/test_cli.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
_copy_files_needed_for_post_processing,
3838
_create_main_version_header,
3939
_determine_bazel_rule,
40+
_get_library_dist_name,
4041
_determine_library_namespace,
4142
_get_library_id,
4243
_get_libraries_to_prepare_for_release,
@@ -52,6 +53,7 @@
5253
_update_changelog_for_library,
5354
_update_global_changelog,
5455
_update_version_for_library,
56+
_verify_library_dist_name,
5557
_verify_library_namespace,
5658
_write_json_file,
5759
_write_text_file,
@@ -525,7 +527,8 @@ def test_handle_build_success(caplog, mocker, mock_build_request_file):
525527
caplog.set_level(logging.INFO)
526528

527529
mocker.patch("cli._run_nox_sessions")
528-
mocker.patch("cli._verify_library_namespace", return_value=True)
530+
mocker.patch("cli._verify_library_namespace")
531+
mocker.patch("cli._verify_library_dist_name")
529532
handle_build()
530533

531534
assert "'build' command executed." in caplog.text
@@ -922,6 +925,26 @@ def test_determine_library_namespace_fails_not_subpath():
922925
_determine_library_namespace(gapic_parent_path, pkg_root_path)
923926

924927

928+
def test_get_library_dist_name_success(mocker):
929+
mock_metadata = {"name": "my-lib", "version": "1.0.0"}
930+
mocker.patch("build.util.project_wheel_metadata", return_value=mock_metadata)
931+
assert _get_library_dist_name("my-lib", "repo") == "my-lib"
932+
933+
934+
def test_verify_library_dist_name_setup_success(mocker):
935+
"""Tests success when a library distribution name in setup.py is valid."""
936+
mock_setup_file = mocker.patch("cli._get_library_dist_name", return_value="my-lib")
937+
_verify_library_dist_name("my-lib", "repo")
938+
mock_setup_file.assert_called_once_with("my-lib", "repo")
939+
940+
941+
def test_verify_library_dist_name_fail(mocker):
942+
"""Tests failure when a library-id does not match the libary distribution name."""
943+
mocker.patch("cli._get_library_dist_name", return_value="invalid-lib")
944+
with pytest.raises(ValueError):
945+
_verify_library_dist_name("my-lib", "repo")
946+
947+
925948
def test_verify_library_namespace_success_valid(mocker, mock_path_class):
926949
"""Tests success when a single valid namespace is found."""
927950
# 1. Get the mock instance from the mock class's return_value

0 commit comments

Comments
 (0)