|
24 | 24 | import os |
25 | 25 | import sys |
26 | 26 | from types import MappingProxyType |
27 | | -from typing import Callable, Container, Dict, FrozenSet, Mapping, Optional, Sequence, Set, Tuple |
| 27 | +from typing import Callable, Container, Dict, FrozenSet, Iterable, Mapping, Optional, Sequence, Set, Tuple |
28 | 28 | import yaml |
29 | 29 |
|
30 | 30 | from google.api_core import exceptions |
@@ -237,6 +237,95 @@ def disambiguate(self, string: str) -> str: |
237 | 237 | return self.disambiguate(f'_{string}') |
238 | 238 | return string |
239 | 239 |
|
| 240 | + def add_to_address_allowlist(self, *, |
| 241 | + address_allowlist: Set['metadata.Address'], |
| 242 | + method_allowlist: Set[str], |
| 243 | + resource_messages: Dict[str, 'wrappers.MessageType'], |
| 244 | + ) -> None: |
| 245 | + """Adds to the set of Addresses of wrapper objects to be included in selective GAPIC generation. |
| 246 | +
|
| 247 | + This method is used to create an allowlist of addresses to be used to filter out unneeded |
| 248 | + services, methods, messages, and enums at a later step. |
| 249 | +
|
| 250 | + Args: |
| 251 | + address_allowlist (Set[metadata.Address]): A set of allowlisted metadata.Address |
| 252 | + objects to add to. Only the addresses of the allowlisted methods, the services |
| 253 | + containing these methods, and messages/enums those methods use will be part of the |
| 254 | + final address_allowlist. The set may be modified during this call. |
| 255 | + method_allowlist (Set[str]): An allowlist of fully-qualified method names. |
| 256 | + resource_messages (Dict[str, wrappers.MessageType]): A dictionary mapping the unified |
| 257 | + resource type name of a resource message to the corresponding MessageType object |
| 258 | + representing that resource message. Only resources with a message representation |
| 259 | + should be included in the dictionary. |
| 260 | + Returns: |
| 261 | + None |
| 262 | + """ |
| 263 | + # The method.operation_service for an extended LRO is not fully qualified, so we |
| 264 | + # truncate the service names accordingly so they can be found in |
| 265 | + # method.add_to_address_allowlist |
| 266 | + services_in_proto = { |
| 267 | + service.name: service for service in self.services.values() |
| 268 | + } |
| 269 | + for service in self.services.values(): |
| 270 | + service.add_to_address_allowlist(address_allowlist=address_allowlist, |
| 271 | + method_allowlist=method_allowlist, |
| 272 | + resource_messages=resource_messages, |
| 273 | + services_in_proto=services_in_proto) |
| 274 | + |
| 275 | + def prune_messages_for_selective_generation(self, *, |
| 276 | + address_allowlist: Set['metadata.Address']) -> Optional['Proto']: |
| 277 | + """Returns a truncated version of this Proto. |
| 278 | +
|
| 279 | + Only the services, messages, and enums contained in the allowlist |
| 280 | + of visited addresses are included in the returned object. If there |
| 281 | + are no services, messages, or enums left, and no file level resources, |
| 282 | + return None. |
| 283 | +
|
| 284 | + Args: |
| 285 | + address_allowlist (Set[metadata.Address]): A set of allowlisted metadata.Address |
| 286 | + objects to filter against. Objects with addresses not the allowlist will be |
| 287 | + removed from the returned Proto. |
| 288 | + Returns: |
| 289 | + Optional[Proto]: A truncated version of this proto. If there are no services, messages, |
| 290 | + or enums left after the truncation process and there are no file level resources, |
| 291 | + returns None. |
| 292 | + """ |
| 293 | + # Once the address allowlist has been created, it suffices to only |
| 294 | + # prune items at 2 different levels to truncate the Proto object: |
| 295 | + # |
| 296 | + # 1. At the Proto level, we remove unnecessary services, messages, |
| 297 | + # and enums. |
| 298 | + # 2. For allowlisted services, at the Service level, we remove |
| 299 | + # non-allowlisted methods. |
| 300 | + services = { |
| 301 | + k: v.prune_messages_for_selective_generation( |
| 302 | + address_allowlist=address_allowlist) |
| 303 | + for k, v in self.services.items() |
| 304 | + if v.meta.address in address_allowlist |
| 305 | + } |
| 306 | + |
| 307 | + all_messages = { |
| 308 | + k: v |
| 309 | + for k, v in self.all_messages.items() |
| 310 | + if v.ident in address_allowlist |
| 311 | + } |
| 312 | + |
| 313 | + all_enums = { |
| 314 | + k: v |
| 315 | + for k, v in self.all_enums.items() |
| 316 | + if v.ident in address_allowlist |
| 317 | + } |
| 318 | + |
| 319 | + if not services and not all_messages and not all_enums: |
| 320 | + return None |
| 321 | + |
| 322 | + return dataclasses.replace( |
| 323 | + self, |
| 324 | + services=services, |
| 325 | + all_messages=all_messages, |
| 326 | + all_enums=all_enums |
| 327 | + ) |
| 328 | + |
240 | 329 |
|
241 | 330 | @dataclasses.dataclass(frozen=True) |
242 | 331 | class API: |
@@ -365,10 +454,52 @@ def disambiguate_keyword_sanitize_fname( |
365 | 454 | ignore_unknown_fields=True |
366 | 455 | ) |
367 | 456 |
|
368 | | - # Done; return the API. |
369 | | - return cls(naming=naming, |
370 | | - all_protos=protos, |
371 | | - service_yaml_config=service_yaml_config) |
| 457 | + # Third pass for various selective GAPIC settings; these require |
| 458 | + # settings in the service.yaml and so we build the API object |
| 459 | + # before doing another pass. |
| 460 | + api = cls(naming=naming, |
| 461 | + all_protos=protos, |
| 462 | + service_yaml_config=service_yaml_config) |
| 463 | + |
| 464 | + if package in api.all_library_settings: |
| 465 | + selective_gapic_methods = set( |
| 466 | + api.all_library_settings[package].python_settings.common.selective_gapic_generation.methods |
| 467 | + ) |
| 468 | + if selective_gapic_methods: |
| 469 | + |
| 470 | + all_resource_messages = collections.ChainMap( |
| 471 | + *(proto.resource_messages for proto in protos.values()) |
| 472 | + ) |
| 473 | + |
| 474 | + # Prepare a list of addresses to include in selective generation, |
| 475 | + # then prune each Proto object. We look at metadata.Addresses, not objects, because |
| 476 | + # objects that refer to the same thing in the proto are different Python objects |
| 477 | + # in memory. |
| 478 | + address_allowlist: Set['metadata.Address'] = set([]) |
| 479 | + for proto in api.protos.values(): |
| 480 | + proto.add_to_address_allowlist(address_allowlist=address_allowlist, |
| 481 | + method_allowlist=selective_gapic_methods, |
| 482 | + resource_messages=all_resource_messages) |
| 483 | + |
| 484 | + # The list of explicitly allow-listed protos to generate, plus all |
| 485 | + # the proto dependencies regardless of the allow-list. |
| 486 | + new_all_protos = {} |
| 487 | + |
| 488 | + # We only prune services/messages/enums from protos that are not dependencies. |
| 489 | + for name, proto in api.all_protos.items(): |
| 490 | + if name not in api.protos: |
| 491 | + new_all_protos[name] = proto |
| 492 | + else: |
| 493 | + proto_to_generate = proto.prune_messages_for_selective_generation( |
| 494 | + address_allowlist=address_allowlist) |
| 495 | + if proto_to_generate: |
| 496 | + new_all_protos[name] = proto_to_generate |
| 497 | + |
| 498 | + api = cls(naming=naming, |
| 499 | + all_protos=new_all_protos, |
| 500 | + service_yaml_config=service_yaml_config) |
| 501 | + |
| 502 | + return api |
372 | 503 |
|
373 | 504 | @cached_property |
374 | 505 | def enums(self) -> Mapping[str, wrappers.EnumType]: |
@@ -743,6 +874,21 @@ def enforce_valid_library_settings( |
743 | 874 | continue |
744 | 875 | versions_seen.add(library_settings.version) |
745 | 876 |
|
| 877 | + # Check to see if selective gapic generation methods are valid. |
| 878 | + selective_gapic_errors = {} |
| 879 | + for method_name in library_settings.python_settings.common.selective_gapic_generation.methods: |
| 880 | + if method_name not in self.all_methods: |
| 881 | + selective_gapic_errors[method_name] = "Method does not exist." |
| 882 | + elif not method_name.startswith(library_settings.version): |
| 883 | + selective_gapic_errors[method_name] = "Mismatched version for method." |
| 884 | + |
| 885 | + if selective_gapic_errors: |
| 886 | + all_errors[library_settings.version] = [ |
| 887 | + { |
| 888 | + "selective_gapic_generation": selective_gapic_errors, |
| 889 | + } |
| 890 | + ] |
| 891 | + |
746 | 892 | if all_errors: |
747 | 893 | raise ClientLibrarySettingsError(yaml.dump(all_errors)) |
748 | 894 |
|
|
0 commit comments