@@ -259,114 +259,60 @@ def disambiguate(self, string: str) -> str:
259259 return self .disambiguate (f"_{ string } " )
260260 return string
261261
262- def add_to_address_allowlist (
262+ def with_selective_generation (
263263 self ,
264264 * ,
265- address_allowlist : Set ["metadata.Address" ],
266- method_allowlist : Set [str ],
267- resource_messages : Dict [str , "wrappers.MessageType" ],
268- ) -> None :
269- """Adds to the set of Addresses of wrapper objects to be included in selective GAPIC generation.
270-
271- This method is used to create an allowlist of addresses to be used to filter out unneeded
272- services, methods, messages, and enums at a later step.
273-
274- Args:
275- address_allowlist (Set[metadata.Address]): A set of allowlisted metadata.Address
276- objects to add to. Only the addresses of the allowlisted methods, the services
277- containing these methods, and messages/enums those methods use will be part of the
278- final address_allowlist. The set may be modified during this call.
279- method_allowlist (Set[str]): An allowlist of fully-qualified method names.
280- resource_messages (Dict[str, wrappers.MessageType]): A dictionary mapping the unified
281- resource type name of a resource message to the corresponding MessageType object
282- representing that resource message. Only resources with a message representation
283- should be included in the dictionary.
284- Returns:
285- None
286- """
287- # The method.operation_service for an extended LRO is not fully qualified, so we
288- # truncate the service names accordingly so they can be found in
289- # method.add_to_address_allowlist
290- services_in_proto = {
291- service .name : service for service in self .services .values ()
292- }
293- for service in self .services .values ():
294- service .add_to_address_allowlist (
295- address_allowlist = address_allowlist ,
296- method_allowlist = method_allowlist ,
297- resource_messages = resource_messages ,
298- services_in_proto = services_in_proto ,
299- )
300-
301- def prune_messages_for_selective_generation (
302- self , * , address_allowlist : Set ["metadata.Address" ]
265+ generate_omitted_as_internal : bool ,
266+ public_methods : Set [str ],
267+ excluded_addresses : Set ["metadata.Address" ],
303268 ) -> Optional ["Proto" ]:
304- """Returns a truncated version of this Proto.
305-
306- Only the services, messages, and enums contained in the allowlist
307- of visited addresses are included in the returned object. If there
308- are no services, messages, or enums left, and no file level resources,
309- return None.
269+ """Returns a version of this Proto for selective generation.
310270
311271 Args:
312- address_allowlist (Set[metadata.Address]): A set of allowlisted metadata.Address
313- objects to filter against. Objects with addresses not the allowlist will be
314- removed from the returned Proto.
272+ generate_omitted_as_internal (bool): Whether to mark omitted methods as internal.
273+ public_methods (Set[str]): The set of fully-qualified method names to keep as public.
274+ excluded_addresses (Set[metadata.Address]): The set of addresses to exclude from generation.
275+
315276 Returns:
316- Optional[Proto]: A truncated version of this proto. If there are no services, messages,
317- or enums left after the truncation process and there are no file level resources,
318- returns None.
277+ Optional[Proto]: A version of this Proto with services/methods filtered.
278+ Returns None if the Proto becomes empty and generate_omitted_as_internal is False.
319279 """
320- # Once the address allowlist has been created, it suffices to only
321- # prune items at 2 different levels to truncate the Proto object:
280+ services = {}
281+ for k , v in self .services .items ():
282+ new_v = v .with_selective_generation (
283+ generate_omitted_as_internal = generate_omitted_as_internal ,
284+ public_methods = public_methods ,
285+ excluded_addresses = excluded_addresses )
286+ if new_v :
287+ services [k ] = new_v
288+
289+ # We only prune messages/enums from protos that are not dependencies.
290+ # A message or enum is excluded IF AND ONLY IF:
291+ # 1. It is a top-level request or response message for an omitted RPC.
292+ # 2. It is NOT reachable from any publicly allowed RPC.
322293 #
323- # 1. At the Proto level, we remove unnecessary services, messages,
324- # and enums.
325- # 2. For allowlisted services, at the Service level, we remove
326- # non-allowlisted methods.
327- services = {
328- k : v .prune_messages_for_selective_generation (
329- address_allowlist = address_allowlist
330- )
331- for k , v in self .services .items ()
332- if v .meta .address in address_allowlist
333- }
334-
294+ # This ensures that shared messages, messages not attached to any RPC,
295+ # and messages reachable via other paths (like LRO response types) are KEPT.
335296 all_messages = {
336- k : v for k , v in self .all_messages .items () if v .ident in address_allowlist
297+ k : v for k , v in self .all_messages .items () if v .ident not in excluded_addresses
337298 }
338299
339300 all_enums = {
340- k : v for k , v in self .all_enums .items () if v .ident in address_allowlist
301+ k : v for k , v in self .all_enums .items () if v .ident not in excluded_addresses
341302 }
342303
343- if not services and not all_messages and not all_enums :
304+ # If the proto becomes empty after pruning, we return None to signal
305+ # that it should be excluded from generation.
306+ if not generate_omitted_as_internal and not services and not all_messages and not all_enums :
344307 return None
345308
346309 return dataclasses .replace (
347- self , services = services , all_messages = all_messages , all_enums = all_enums
310+ self ,
311+ services = services ,
312+ all_messages = all_messages ,
313+ all_enums = all_enums ,
348314 )
349315
350- def with_internal_methods (self , * , public_methods : Set [str ]) -> "Proto" :
351- """Returns a version of this Proto with some Methods marked as internal.
352-
353- The methods not in the public_methods set will be marked as internal and
354- services containing these methods will also be marked as internal by extension.
355- (See :meth:`Service.is_internal` for more details).
356-
357- Args:
358- public_methods (Set[str]): An allowlist of fully-qualified method names.
359- Methods not in this allowlist will be marked as internal.
360- Returns:
361- Proto: A version of this Proto with Method objects corresponding to methods
362- not in `public_methods` marked as internal.
363- """
364- services = {
365- k : v .with_internal_methods (public_methods = public_methods )
366- for k , v in self .services .items ()
367- }
368- return dataclasses .replace (self , services = services )
369-
370316
371317@dataclasses .dataclass (frozen = True )
372318class API :
@@ -530,37 +476,82 @@ def disambiguate_keyword_sanitize_fname(
530476 k : v for k , v in api .all_protos .items () if k not in api .protos
531477 }
532478
533- if selective_gapic_settings .generate_omitted_as_internal :
534- for name , proto in api .protos .items ():
535- new_all_protos [name ] = proto .with_internal_methods (
536- public_methods = selective_gapic_methods
537- )
538- else :
539- all_resource_messages = collections .ChainMap (
540- * (proto .resource_messages for proto in protos .values ())
541- )
479+ all_resource_messages = collections .ChainMap (
480+ * (proto .resource_messages for proto in api .all_protos .values ())
481+ )
542482
543- # Prepare a list of addresses to include in selective generation,
544- # then prune each Proto object. We look at metadata.Addresses, not objects, because
545- # objects that refer to the same thing in the proto are different Python objects
546- # in memory.
547- address_allowlist : Set ["metadata.Address" ] = set ([])
548- for proto in api .protos .values ():
549- proto .add_to_address_allowlist (
550- address_allowlist = address_allowlist ,
483+ # Create a global map of services to support cross-proto lookup
484+ # for extended LROs.
485+ #
486+ # Note: This is keyed by the Address object itself (which is
487+ # hashable by its proto name) to ensure compatibility with
488+ # Address.resolve() lookups in wrappers.py.
489+ all_services : Dict [metadata .Address , wrappers .Service ] = {}
490+ for p in api .all_protos .values ():
491+ for s in p .services .values ():
492+ all_services [s .meta .address ] = s
493+
494+ # Calculate addresses of omitted RPCs and their top-level request/response messages.
495+ # These are "candidates" for exclusion.
496+ #
497+ # We only consider top-level request/response messages of omitted RPCs as
498+ # candidates for exclusion. This is conservative: it ensures that:
499+ # - Messages NOT used by any RPC are KEPT (e.g. for user convenience).
500+ # - Messages shared between an omitted and a kept RPC are KEPT.
501+ # - Messages reachable from a kept RPC but NOT as a top-level request/response
502+ # (e.g. nested messages) are KEPT.
503+ candidate_excluded_addresses : Set ["metadata.Address" ] = set ([])
504+ for proto in api .all_protos .values ():
505+ for service in proto .services .values ():
506+ for method in service .methods .values ():
507+ if method .ident .proto not in selective_gapic_methods :
508+ # Candidate for exclusion: the method itself and its direct request/response types.
509+ candidate_excluded_addresses .add (method .meta .address )
510+ candidate_excluded_addresses .add (method .input .ident )
511+ candidate_excluded_addresses .add (method .output .ident )
512+
513+ # If this is an LRO, add its response and metadata types to candidates.
514+ if method .lro :
515+ candidate_excluded_addresses .add (method .lro .response_type .ident )
516+ candidate_excluded_addresses .add (method .lro .metadata_type .ident )
517+
518+ # If this is an extended LRO, add its request and operation types to candidates.
519+ if method .extended_lro :
520+ candidate_excluded_addresses .add (method .extended_lro .request_type .ident )
521+ candidate_excluded_addresses .add (method .extended_lro .operation_type .ident )
522+
523+ # Calculate publicly reachable addresses (API-wide).
524+ # This includes all types reachable from the allowlisted (public) methods.
525+ public_rpc_addresses : Set ["metadata.Address" ] = set ([])
526+ for proto in api .all_protos .values ():
527+ for service in proto .services .values ():
528+ service .add_to_address_allowlist (
529+ address_allowlist = public_rpc_addresses ,
551530 method_allowlist = selective_gapic_methods ,
552531 resource_messages = all_resource_messages ,
532+ services_in_proto = all_services ,
553533 )
554534
555- # We only prune services/messages/enums from protos that are not dependencies.
556- for name , proto in api .protos .items ():
557- proto_to_generate = (
558- proto .prune_messages_for_selective_generation (
559- address_allowlist = address_allowlist
560- )
561- )
562- if proto_to_generate :
563- new_all_protos [name ] = proto_to_generate
535+ # Addresses to exclude: those that are candidates for exclusion but NOT
536+ # reachable from any PUBLIC RPC.
537+ #
538+ # This set difference effectively "vets" the candidates. If a candidate
539+ # message is actually reachable from a public RPC, it's removed from
540+ # the exclusion list.
541+ excluded_addresses = (
542+ candidate_excluded_addresses - public_rpc_addresses
543+ if not selective_gapic_settings .generate_omitted_as_internal
544+ else set ([])
545+ )
546+
547+ for name , proto in api .protos .items ():
548+ proto_to_generate = proto .with_selective_generation (
549+ generate_omitted_as_internal = selective_gapic_settings .generate_omitted_as_internal ,
550+ public_methods = selective_gapic_methods ,
551+ excluded_addresses = excluded_addresses ,
552+ )
553+ if proto_to_generate :
554+ new_all_protos [name ] = proto_to_generate
564555
565556 api = cls (
566557 naming = naming ,
@@ -1514,8 +1505,8 @@ def _maybe_get_lro(
15141505 response_key = service_address .resolve (op .response_type )
15151506 metadata_key = service_address .resolve (op .metadata_type )
15161507 lro = wrappers .OperationInfo (
1517- response_type = self .api_messages [response_key ],
1518- metadata_type = self .api_messages [metadata_key ],
1508+ response_type = self .api_messages [response_key . proto ],
1509+ metadata_type = self .api_messages [metadata_key . proto ],
15191510 )
15201511
15211512 return lro
@@ -1558,7 +1549,7 @@ def _maybe_get_extended_lro(
15581549 operation_request_key = service_address .resolve (
15591550 operation_polling_method_pb .input_type .lstrip ("." )
15601551 )
1561- operation_request_message = self .api_messages [operation_request_key ]
1552+ operation_request_message = self .api_messages [operation_request_key . proto ]
15621553
15631554 operation_type = service_address .resolve (
15641555 operation_polling_method_pb .output_type .lstrip ("." )
@@ -1568,12 +1559,12 @@ def _maybe_get_extended_lro(
15681559 raise ValueError (
15691560 f"Inconsistent return types between extended lro method '{ meth_pb .name } '"
15701561 f" and extended lro polling method '{ operation_polling_method_pb .name } ':"
1571- f" '{ method_output_type } ' and '{ operation_type } '"
1562+ f" '{ method_output_type . proto } ' and '{ operation_type . proto } '"
15721563 )
15731564
1574- operation_message = self .api_messages [operation_type ]
1565+ operation_message = self .api_messages [operation_type . proto ]
15751566 if not operation_message .is_extended_operation :
1576- raise ValueError (f"Message is not an extended operation: { operation_type } " )
1567+ raise ValueError (f"Message is not an extended operation: { operation_type . proto } " )
15771568
15781569 return wrappers .ExtendedOperationInfo (
15791570 request_type = operation_request_message ,
0 commit comments