Skip to content

Commit aef1bed

Browse files
authored
chore: allow --api-path as option to generate cmd and generate per api/majorversion (#3712)
This change allow generator to generate for a specified api-path. When "--api-path" is not specified, fallback to "--library-names", and __behavior for existing command usages should not change__. For now, api-path should only take in path to api/version, e.g. google/cloud/functions/v2
1 parent b1b075d commit aef1bed

12 files changed

+318
-100
lines changed

hermetic_build/common/cli/get_changed_libraries.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import click as click
1717

18-
from common.model.generation_config import from_yaml
18+
from common.model.generation_config import GenerationConfig
1919
from common.utils.generation_config_comparator import compare_config
2020

2121

@@ -69,8 +69,8 @@ def create(
6969
"current-generation-config-path."
7070
)
7171
config_change = compare_config(
72-
baseline_config=from_yaml(baseline_generation_config_path),
73-
current_config=from_yaml(current_generation_config_path),
72+
baseline_config=GenerationConfig.from_yaml(baseline_generation_config_path),
73+
current_config=GenerationConfig.from_yaml(current_generation_config_path),
7474
)
7575
click.echo(",".join(config_change.get_changed_libraries()))
7676

hermetic_build/common/model/generation_config.py

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -105,77 +105,77 @@ def __validate(self) -> None:
105105
)
106106
seen_library_names[library_name] = library.name_pretty
107107

108+
@staticmethod
109+
def from_yaml(path_to_yaml: str) -> "GenerationConfig":
110+
"""
111+
Parses a yaml located in path_to_yaml.
112+
:param path_to_yaml: the path to the configuration file
113+
:return the parsed configuration represented by the "model" classes
114+
"""
115+
with open(path_to_yaml, "r") as file_stream:
116+
config = yaml.safe_load(file_stream)
117+
118+
libraries = _required(config, "libraries", REPO_LEVEL_PARAMETER)
119+
if not libraries:
120+
raise ValueError(f"Library is None in {path_to_yaml}.")
121+
122+
parsed_libraries = list()
123+
for library in libraries:
124+
gapics = _required(library, "GAPICs")
125+
126+
parsed_gapics = list()
127+
if not gapics:
128+
raise ValueError(f"GAPICs is None in {library}.")
129+
for gapic in gapics:
130+
proto_path = _required(gapic, "proto_path", GAPIC_LEVEL_PARAMETER)
131+
new_gapic = GapicConfig(proto_path)
132+
parsed_gapics.append(new_gapic)
133+
134+
new_library = LibraryConfig(
135+
api_shortname=_required(library, "api_shortname"),
136+
api_description=_required(library, "api_description"),
137+
name_pretty=_required(library, "name_pretty"),
138+
product_documentation=_required(library, "product_documentation"),
139+
gapic_configs=parsed_gapics,
140+
library_type=_optional(library, "library_type", "GAPIC_AUTO"),
141+
release_level=_optional(library, "release_level", "preview"),
142+
api_id=_optional(library, "api_id", None),
143+
api_reference=_optional(library, "api_reference", None),
144+
codeowner_team=_optional(library, "codeowner_team", None),
145+
excluded_poms=_optional(library, "excluded_poms", None),
146+
excluded_dependencies=_optional(library, "excluded_dependencies", None),
147+
client_documentation=_optional(library, "client_documentation", None),
148+
distribution_name=_optional(library, "distribution_name", None),
149+
googleapis_commitish=_optional(library, "googleapis_commitish", None),
150+
group_id=_optional(library, "group_id", "com.google.cloud"),
151+
issue_tracker=_optional(library, "issue_tracker", None),
152+
library_name=_optional(library, "library_name", None),
153+
rest_documentation=_optional(library, "rest_documentation", None),
154+
rpc_documentation=_optional(library, "rpc_documentation", None),
155+
cloud_api=_optional(library, "cloud_api", True),
156+
requires_billing=_optional(library, "requires_billing", True),
157+
extra_versioned_modules=_optional(
158+
library, "extra_versioned_modules", None
159+
),
160+
recommended_package=_optional(library, "recommended_package", None),
161+
min_java_version=_optional(library, "min_java_version", None),
162+
transport=_optional(library, "transport", None),
163+
)
164+
parsed_libraries.append(new_library)
108165

109-
def from_yaml(path_to_yaml: str) -> GenerationConfig:
110-
"""
111-
Parses a yaml located in path_to_yaml.
112-
:param path_to_yaml: the path to the configuration file
113-
:return the parsed configuration represented by the "model" classes
114-
"""
115-
with open(path_to_yaml, "r") as file_stream:
116-
config = yaml.safe_load(file_stream)
117-
118-
libraries = __required(config, "libraries", REPO_LEVEL_PARAMETER)
119-
if not libraries:
120-
raise ValueError(f"Library is None in {path_to_yaml}.")
121-
122-
parsed_libraries = list()
123-
for library in libraries:
124-
gapics = __required(library, "GAPICs")
125-
126-
parsed_gapics = list()
127-
if not gapics:
128-
raise ValueError(f"GAPICs is None in {library}.")
129-
for gapic in gapics:
130-
proto_path = __required(gapic, "proto_path", GAPIC_LEVEL_PARAMETER)
131-
new_gapic = GapicConfig(proto_path)
132-
parsed_gapics.append(new_gapic)
133-
134-
new_library = LibraryConfig(
135-
api_shortname=__required(library, "api_shortname"),
136-
api_description=__required(library, "api_description"),
137-
name_pretty=__required(library, "name_pretty"),
138-
product_documentation=__required(library, "product_documentation"),
139-
gapic_configs=parsed_gapics,
140-
library_type=__optional(library, "library_type", "GAPIC_AUTO"),
141-
release_level=__optional(library, "release_level", "preview"),
142-
api_id=__optional(library, "api_id", None),
143-
api_reference=__optional(library, "api_reference", None),
144-
codeowner_team=__optional(library, "codeowner_team", None),
145-
excluded_poms=__optional(library, "excluded_poms", None),
146-
excluded_dependencies=__optional(library, "excluded_dependencies", None),
147-
client_documentation=__optional(library, "client_documentation", None),
148-
distribution_name=__optional(library, "distribution_name", None),
149-
googleapis_commitish=__optional(library, "googleapis_commitish", None),
150-
group_id=__optional(library, "group_id", "com.google.cloud"),
151-
issue_tracker=__optional(library, "issue_tracker", None),
152-
library_name=__optional(library, "library_name", None),
153-
rest_documentation=__optional(library, "rest_documentation", None),
154-
rpc_documentation=__optional(library, "rpc_documentation", None),
155-
cloud_api=__optional(library, "cloud_api", True),
156-
requires_billing=__optional(library, "requires_billing", True),
157-
extra_versioned_modules=__optional(
158-
library, "extra_versioned_modules", None
166+
parsed_config = GenerationConfig(
167+
googleapis_commitish=_required(
168+
config, "googleapis_commitish", REPO_LEVEL_PARAMETER
159169
),
160-
recommended_package=__optional(library, "recommended_package", None),
161-
min_java_version=__optional(library, "min_java_version", None),
162-
transport=__optional(library, "transport", None),
170+
gapic_generator_version=_optional(config, GAPIC_GENERATOR_VERSION, None),
171+
libraries_bom_version=_optional(config, LIBRARIES_BOM_VERSION, None),
172+
libraries=parsed_libraries,
163173
)
164-
parsed_libraries.append(new_library)
165-
166-
parsed_config = GenerationConfig(
167-
googleapis_commitish=__required(
168-
config, "googleapis_commitish", REPO_LEVEL_PARAMETER
169-
),
170-
gapic_generator_version=__optional(config, GAPIC_GENERATOR_VERSION, None),
171-
libraries_bom_version=__optional(config, LIBRARIES_BOM_VERSION, None),
172-
libraries=parsed_libraries,
173-
)
174174

175-
return parsed_config
175+
return parsed_config
176176

177177

178-
def __required(config: dict, key: str, level: str = LIBRARY_LEVEL_PARAMETER):
178+
def _required(config: dict, key: str, level: str = LIBRARY_LEVEL_PARAMETER):
179179
if key not in config:
180180
message = (
181181
f"{level}, {key}, is not found in {config} in yaml."
@@ -186,7 +186,7 @@ def __required(config: dict, key: str, level: str = LIBRARY_LEVEL_PARAMETER):
186186
return config[key]
187187

188188

189-
def __optional(config: dict, key: str, default: any):
189+
def _optional(config: dict, key: str, default: any):
190190
if key not in config:
191191
return default
192192
return config[key]

hermetic_build/common/model/library_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ def __init__(
8282
self.distribution_name = self.__get_distribution_name(distribution_name)
8383
self.transport = self.__validate_transport(transport)
8484

85+
def set_gapic_configs(self, gapic_configs: list[GapicConfig]) -> None:
86+
"""
87+
Sets the gapic_configs for the library.
88+
89+
Args:
90+
gapic_configs: The new list of GapicConfig objects.
91+
"""
92+
self.gapic_configs = gapic_configs
93+
8594
def get_library_name(self) -> str:
8695
"""
8796
Return the library name of a given LibraryConfig object

hermetic_build/common/tests/model/generation_config_unit_tests.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import os
1515
import unittest
1616
from pathlib import Path
17-
from common.model.generation_config import from_yaml, GenerationConfig
17+
from common.model.generation_config import GenerationConfig
1818
from common.model.library_config import LibraryConfig
1919

2020
script_dir = os.path.dirname(os.path.realpath(__file__))
@@ -72,7 +72,7 @@ def test_generation_config_set_generator_version_from_env(self):
7272
os.environ.pop("GENERATOR_VERSION")
7373

7474
def test_from_yaml_succeeds(self):
75-
config = from_yaml(f"{test_config_dir}/generation_config.yaml")
75+
config = GenerationConfig.from_yaml(f"{test_config_dir}/generation_config.yaml")
7676
self.assertEqual("2.34.0", config.gapic_generator_version)
7777
self.assertEqual(
7878
"1a45bf7393b52407188c82e63101db7dc9c72026", config.googleapis_commitish
@@ -105,7 +105,7 @@ def test_from_yaml_succeeds(self):
105105
self.assertEqual("google/cloud/asset/v1p7beta1", gapics[4].proto_path)
106106

107107
def test_get_proto_path_to_library_name_success(self):
108-
paths = from_yaml(
108+
paths = GenerationConfig.from_yaml(
109109
f"{test_config_dir}/generation_config.yaml"
110110
).get_proto_path_to_library_name()
111111
self.assertEqual(
@@ -181,78 +181,78 @@ def test_from_yaml_without_googleapis_commitish_raise_exception(self):
181181
self.assertRaisesRegex(
182182
ValueError,
183183
"Repo level parameter, googleapis_commitish",
184-
from_yaml,
184+
GenerationConfig.from_yaml,
185185
f"{test_config_dir}/config_without_googleapis.yaml",
186186
)
187187

188188
def test_from_yaml_without_libraries_raise_exception(self):
189189
self.assertRaisesRegex(
190190
ValueError,
191191
"Repo level parameter, libraries",
192-
from_yaml,
192+
GenerationConfig.from_yaml,
193193
f"{test_config_dir}/config_without_libraries.yaml",
194194
)
195195

196196
def test_from_yaml_without_api_shortname_raise_exception(self):
197197
self.assertRaisesRegex(
198198
ValueError,
199199
"Library level parameter, api_shortname",
200-
from_yaml,
200+
GenerationConfig.from_yaml,
201201
f"{test_config_dir}/config_without_api_shortname.yaml",
202202
)
203203

204204
def test_from_yaml_without_api_description_raise_exception(self):
205205
self.assertRaisesRegex(
206206
ValueError,
207207
r"Library level parameter, api_description.*'api_shortname': 'apigeeconnect'.*",
208-
from_yaml,
208+
GenerationConfig.from_yaml,
209209
f"{test_config_dir}/config_without_api_description.yaml",
210210
)
211211

212212
def test_from_yaml_without_name_pretty_raise_exception(self):
213213
self.assertRaisesRegex(
214214
ValueError,
215215
r"Library level parameter, name_pretty.*'api_shortname': 'apigeeconnect'.*",
216-
from_yaml,
216+
GenerationConfig.from_yaml,
217217
f"{test_config_dir}/config_without_name_pretty.yaml",
218218
)
219219

220220
def test_from_yaml_without_product_documentation_raise_exception(self):
221221
self.assertRaisesRegex(
222222
ValueError,
223223
r"Library level parameter, product_documentation.*'api_shortname': 'apigeeconnect'.*",
224-
from_yaml,
224+
GenerationConfig.from_yaml,
225225
f"{test_config_dir}/config_without_product_docs.yaml",
226226
)
227227

228228
def test_from_yaml_without_gapics_raise_exception(self):
229229
self.assertRaisesRegex(
230230
ValueError,
231231
"Library level parameter, GAPICs.*'api_shortname': 'apigeeconnect'.*",
232-
from_yaml,
232+
GenerationConfig.from_yaml,
233233
f"{test_config_dir}/config_without_gapics_key.yaml",
234234
)
235235

236236
def test_from_yaml_without_proto_path_raise_exception(self):
237237
self.assertRaisesRegex(
238238
ValueError,
239239
"GAPIC level parameter, proto_path",
240-
from_yaml,
240+
GenerationConfig.from_yaml,
241241
f"{test_config_dir}/config_without_proto_path.yaml",
242242
)
243243

244244
def test_from_yaml_with_zero_library_raise_exception(self):
245245
self.assertRaisesRegex(
246246
ValueError,
247247
"Library is None",
248-
from_yaml,
248+
GenerationConfig.from_yaml,
249249
f"{test_config_dir}/config_without_library_value.yaml",
250250
)
251251

252252
def test_from_yaml_with_zero_proto_path_raise_exception(self):
253253
self.assertRaisesRegex(
254254
ValueError,
255255
r"GAPICs is None in.*'api_shortname': 'apigeeconnect'.*",
256-
from_yaml,
256+
GenerationConfig.from_yaml,
257257
f"{test_config_dir}/config_without_gapics_value.yaml",
258258
)

hermetic_build/common/tests/utils/proto_path_utils_unit_tests.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import unittest
1717
from pathlib import Path
1818
from common.utils.proto_path_utils import find_versioned_proto_path
19+
from common.utils.proto_path_utils import ends_with_version
1920

2021
script_dir = os.path.dirname(os.path.realpath(__file__))
2122
resources_dir = os.path.join(script_dir, "..", "resources")
@@ -37,3 +38,20 @@ def test_find_versioned_proto_without_version_return_itself(self):
3738
proto_path = "google/type/color.proto"
3839
expected = "google/type/color.proto"
3940
self.assertEqual(expected, find_versioned_proto_path(proto_path))
41+
42+
def test_ends_with_version_valid(self):
43+
self.assertTrue(ends_with_version("google/cloud/gsuiteaddons/v1"))
44+
self.assertTrue(ends_with_version("google/iam/v1beta"))
45+
self.assertTrue(ends_with_version("google/iam/v2betav1"))
46+
self.assertTrue(ends_with_version("google/cloud/alloydb/connectors/v1alpha"))
47+
self.assertTrue(ends_with_version("v1"))
48+
self.assertTrue(ends_with_version("anything/v123"))
49+
50+
def test_ends_with_version_invalid(self):
51+
self.assertFalse(ends_with_version("google/apps/script/type"))
52+
self.assertFalse(ends_with_version("google/apps/script/type/docs"))
53+
self.assertFalse(
54+
ends_with_version("google/cloud/alloydb/connectors/v123/something")
55+
)
56+
self.assertFalse(ends_with_version(""))
57+
self.assertFalse(ends_with_version("noVersion"))

hermetic_build/common/utils/proto_path_utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,21 @@ def find_versioned_proto_path(proto_path: str) -> str:
3131
idx = proto_path.find(version)
3232
return proto_path[:idx] + version
3333
return proto_path
34+
35+
36+
def ends_with_version(proto_path: str) -> bool:
37+
"""
38+
Checks if a given proto_path string ends with a version identifier.
39+
40+
:param proto_path: The proto_path string to check.
41+
42+
:return:
43+
True if the proto_path ends with a version, False otherwise.
44+
"""
45+
version_regex = re.compile(r"^v[1-9].*")
46+
parts = proto_path.rsplit("/", 1)
47+
if len(parts) > 1:
48+
last_part = parts[1]
49+
else:
50+
last_part = parts[0]
51+
return bool(version_regex.match(last_part))

0 commit comments

Comments
 (0)