Skip to content

Commit eb8a69e

Browse files
gkevinzhengparthea
andauthored
feat: Added support for internal methods in selective GAPIC generation (#2325)
Co-authored-by: Anthonios Partheniou <partheniou@google.com>
1 parent ccd619f commit eb8a69e

File tree

175 files changed

+61955
-33
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

175 files changed

+61955
-33
lines changed

DEVELOPMENT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Execute unit tests by running one of the sessions prefixed with `unit-`.
5959
bazel run //tests/integration:credentials_update
6060
bazel run //tests/integration:eventarc_update
6161
bazel run //tests/integration:logging_update
62+
bazel run //tests/integration:logging_internal_update
6263
bazel run //tests/integration:redis_update
6364
bazel run //tests/integration:redis_selective_update
6465
```

gapic/generator/generator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__(self, opts: Options) -> None:
5959

6060
# Add filters which templates require.
6161
self._env.filters["rst"] = utils.rst
62+
self._env.filters["make_private"] = utils.make_private
6263
self._env.filters["snake_case"] = utils.to_snake_case
6364
self._env.filters["camel_case"] = utils.to_camel_case
6465
self._env.filters["sort_lines"] = utils.sort_lines

gapic/samplegen/samplegen.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,12 @@ def generate_sample_specs(api_schema: api.API, *, opts) -> Generator[Dict[str, A
10361036
# Region Tag Format:
10371037
# [{START|END} ${apishortname}_${apiVersion}_generated_${serviceName}_${rpcName}_{sync|async|rest}]
10381038
region_tag = f"{api_short_name}_{api_version}_generated_{service_name}_{rpc_name}_{sync_or_async}"
1039+
1040+
# We assume that the only methods that start with an underscore are internal methods.
1041+
is_internal = rpc_name.startswith("_")
1042+
if is_internal:
1043+
region_tag += "_internal"
1044+
10391045
spec = {
10401046
"rpc": rpc_name,
10411047
"transport": transport,

gapic/schema/api.py

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,26 @@ def prune_messages_for_selective_generation(self, *,
326326
all_enums=all_enums
327327
)
328328

329+
def with_internal_methods(self, *, public_methods: Set[str]) -> 'Proto':
330+
"""Returns a version of this Proto with some Methods marked as internal.
331+
332+
The methods not in the public_methods set will be marked as internal and
333+
services containing these methods will also be marked as internal by extension.
334+
(See :meth:`Service.is_internal` for more details).
335+
336+
Args:
337+
public_methods (Set[str]): An allowlist of fully-qualified method names.
338+
Methods not in this allowlist will be marked as internal.
339+
Returns:
340+
Proto: A version of this Proto with Method objects corresponding to methods
341+
not in `public_methods` marked as internal.
342+
"""
343+
services = {
344+
k: v.with_internal_methods(public_methods=public_methods)
345+
for k, v in self.services.items()
346+
}
347+
return dataclasses.replace(self, services=services)
348+
329349

330350
@dataclasses.dataclass(frozen=True)
331351
class API:
@@ -462,34 +482,43 @@ def disambiguate_keyword_sanitize_fname(
462482
service_yaml_config=service_yaml_config)
463483

464484
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)
485+
selective_gapic_settings = api.all_library_settings[package].python_settings.\
486+
common.selective_gapic_generation
483487

488+
selective_gapic_methods = set(selective_gapic_settings.methods)
489+
if selective_gapic_methods:
484490
# The list of explicitly allow-listed protos to generate, plus all
485491
# the proto dependencies regardless of the allow-list.
486-
new_all_protos = {}
492+
#
493+
# Both selective GAPIC generation settings (omitting + internal) only alter
494+
# protos that are not dependencies, so we iterate over api.all_protos and copy
495+
# all dependencies as is here.
496+
new_all_protos = {
497+
k: v for k, v in api.all_protos.items()
498+
if k not in api.protos
499+
}
500+
501+
if selective_gapic_settings.generate_omitted_as_internal:
502+
for name, proto in api.protos.items():
503+
new_all_protos[name] = proto.with_internal_methods(
504+
public_methods=selective_gapic_methods)
505+
else:
506+
all_resource_messages = collections.ChainMap(
507+
*(proto.resource_messages for proto in protos.values())
508+
)
487509

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:
510+
# Prepare a list of addresses to include in selective generation,
511+
# then prune each Proto object. We look at metadata.Addresses, not objects, because
512+
# objects that refer to the same thing in the proto are different Python objects
513+
# in memory.
514+
address_allowlist: Set['metadata.Address'] = set([])
515+
for proto in api.protos.values():
516+
proto.add_to_address_allowlist(address_allowlist=address_allowlist,
517+
method_allowlist=selective_gapic_methods,
518+
resource_messages=all_resource_messages)
519+
520+
# We only prune services/messages/enums from protos that are not dependencies.
521+
for name, proto in api.protos.items():
493522
proto_to_generate = proto.prune_messages_for_selective_generation(
494523
address_allowlist=address_allowlist)
495524
if proto_to_generate:

gapic/schema/wrappers.py

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from gapic import utils
5454
from gapic.schema import metadata
5555
from gapic.utils import uri_sample
56+
from gapic.utils import make_private
5657

5758

5859
@dataclasses.dataclass(frozen=True)
@@ -900,6 +901,14 @@ def add_to_address_allowlist(self, *,
900901
901902
This method is used to create an allowlist of addresses to be used to filter out unneeded
902903
services, methods, messages, and enums at a later step.
904+
905+
Args:
906+
address_allowlist (Set[metadata.Address]): A set of allowlisted metadata.Address
907+
objects to add to. Only the addresses of the allowlisted methods, the services
908+
containing these methods, and messages/enums those methods use will be part of the
909+
final address_allowlist. The set may be modified during this call.
910+
Returns:
911+
None
903912
"""
904913
address_allowlist.add(self.ident)
905914

@@ -1014,6 +1023,18 @@ def add_to_address_allowlist(self, *,
10141023
10151024
This method is used to create an allowlist of addresses to be used to filter out unneeded
10161025
services, methods, messages, and enums at a later step.
1026+
1027+
Args:
1028+
address_allowlist (Set[metadata.Address]): A set of allowlisted metadata.Address
1029+
objects to add to. Only the addresses of the allowlisted methods, the services
1030+
containing these methods, and messages/enums those methods use will be part of the
1031+
final address_allowlist. The set may be modified during this call.
1032+
resource_messages (Dict[str, wrappers.MessageType]): A dictionary mapping the unified
1033+
resource type name of a resource message to the corresponding MessageType object
1034+
representing that resource message. Only resources with a message representation
1035+
should be included in the dictionary.
1036+
Returns:
1037+
None
10171038
"""
10181039

10191040
self.request_type.add_to_address_allowlist(
@@ -1061,6 +1082,18 @@ def add_to_address_allowlist(self, *,
10611082
10621083
This method is used to create an allowlist of addresses to be used to filter out unneeded
10631084
services, methods, messages, and enums at a later step.
1085+
1086+
Args:
1087+
address_allowlist (Set[metadata.Address]): A set of allowlisted metadata.Address
1088+
objects to add to. Only the addresses of the allowlisted methods, the services
1089+
containing these methods, and messages/enums those methods use will be part of the
1090+
final address_allowlist. The set may be modified during this call.
1091+
resource_messages (Dict[str, wrappers.MessageType]): A dictionary mapping the unified
1092+
resource type name of a resource message to the corresponding MessageType object
1093+
representing that resource message. Only resources with a message representation
1094+
should be included in the dictionary.
1095+
Returns:
1096+
None
10641097
"""
10651098
self.response_type.add_to_address_allowlist(
10661099
address_allowlist=address_allowlist,
@@ -1362,6 +1395,7 @@ class Method:
13621395
method_pb: descriptor_pb2.MethodDescriptorProto
13631396
input: MessageType
13641397
output: MessageType
1398+
is_internal: bool = False
13651399
lro: Optional[OperationInfo] = dataclasses.field(default=None)
13661400
extended_lro: Optional[ExtendedOperationInfo] = dataclasses.field(
13671401
default=None)
@@ -1403,6 +1437,10 @@ def transport_safe_name(self) -> str:
14031437
def is_operation_polling_method(self):
14041438
return self.output.is_extended_operation and self.options.Extensions[ex_ops_pb2.operation_polling_method]
14051439

1440+
@utils.cached_property
1441+
def name(self):
1442+
return make_private(self.method_pb.name) if self.is_internal else self.method_pb.name
1443+
14061444
@utils.cached_property
14071445
def client_output(self):
14081446
return self._client_output(enable_asyncio=False)
@@ -1814,6 +1852,22 @@ def add_to_address_allowlist(self, *,
18141852
18151853
This method is used to create an allowlist of addresses to be used to filter out unneeded
18161854
services, methods, messages, and enums at a later step.
1855+
1856+
Args:
1857+
address_allowlist (Set[metadata.Address]): A set of allowlisted metadata.Address
1858+
objects to add to. Only the addresses of the allowlisted methods, the services
1859+
containing these methods, and messages/enums those methods use will be part of the
1860+
final address_allowlist. The set may be modified during this call.
1861+
method_allowlist (Set[str]): An allowlist of fully-qualified method names.
1862+
resource_messages (Dict[str, wrappers.MessageType]): A dictionary mapping the unified
1863+
resource type name of a resource message to the corresponding MessageType object
1864+
representing that resource message. Only resources with a message representation
1865+
should be included in the dictionary.
1866+
services_in_proto (Dict[str, wrappers.Service]): A dictionary mapping the names of Service
1867+
objects in the proto containing this method to the Service objects. This is necessary
1868+
for traversing the operation service in the case of extended LROs.
1869+
Returns:
1870+
None
18171871
"""
18181872

18191873
address_allowlist.add(self.ident)
@@ -1851,6 +1905,27 @@ def add_to_address_allowlist(self, *,
18511905
resource_messages=resource_messages,
18521906
)
18531907

1908+
def with_internal_methods(self, *, public_methods: Set[str]) -> 'Method':
1909+
"""Returns a version of this ``Method`` marked as internal
1910+
1911+
The methods not in the public_methods set will be marked as internal and
1912+
this ``Service`` will as well by extension (see :meth:`Service.is_internal`).
1913+
1914+
Args:
1915+
public_methods (Set[str]): An allowlist of fully-qualified method names.
1916+
Methods not in this allowlist will be marked as internal.
1917+
Returns:
1918+
Service: A version of this `Service` with `Method` objects corresponding to methods
1919+
not in `public_methods` marked as internal.
1920+
"""
1921+
if self.ident.proto in public_methods:
1922+
return self
1923+
1924+
return dataclasses.replace(
1925+
self,
1926+
is_internal=True,
1927+
)
1928+
18541929

18551930
@dataclasses.dataclass(frozen=True)
18561931
class CommonResource:
@@ -1928,7 +2003,7 @@ def __getattr__(self, name):
19282003
@property
19292004
def client_name(self) -> str:
19302005
"""Returns the name of the generated client class"""
1931-
return self.name + "Client"
2006+
return ("Base" if self.is_internal else "") + self.name + "Client"
19322007

19332008
@property
19342009
def client_package_version(self) -> str:
@@ -1937,7 +2012,7 @@ def client_package_version(self) -> str:
19372012
@property
19382013
def async_client_name(self) -> str:
19392014
"""Returns the name of the generated AsyncIO client class"""
1940-
return self.name + "AsyncClient"
2015+
return ("Base" if self.is_internal else "") + self.name + "AsyncClient"
19412016

19422017
@property
19432018
def transport_name(self):
@@ -2143,6 +2218,10 @@ def operation_polling_method(self) -> Optional[Method]:
21432218
None
21442219
)
21452220

2221+
@utils.cached_property
2222+
def is_internal(self) -> bool:
2223+
return any(m.is_internal for m in self.methods.values())
2224+
21462225
def with_context(self, *,
21472226
collisions: Set[str],
21482227
visited_messages: Optional[Set["MessageType"]] = None,
@@ -2224,3 +2303,32 @@ def prune_messages_for_selective_generation(self, *,
22242303
for k, v in self.methods.items() if v.ident in address_allowlist
22252304
}
22262305
)
2306+
2307+
def with_internal_methods(self, *,
2308+
public_methods: Set[str]) -> 'Service':
2309+
"""Returns a version of this ``Service`` with some Methods marked as internal.
2310+
2311+
The methods not in the public_methods set will be marked as internal and
2312+
this ``Service`` will as well by extension (see :meth:`Service.is_internal`).
2313+
2314+
Args:
2315+
public_methods (Set[str]): An allowlist of fully-qualified method names.
2316+
Methods not in this allowlist will be marked as internal.
2317+
Returns:
2318+
Service: A version of this `Service` with `Method` objects corresponding to methods
2319+
not in `public_methods` marked as internal.
2320+
"""
2321+
2322+
# Internal methods need to be keyed with underscore prefixed method names
2323+
# (e.g. google.Service.Method -> google.Service._Method) in order for
2324+
# samplegen to work properly.
2325+
return dataclasses.replace(
2326+
self,
2327+
methods={
2328+
meth.name: meth
2329+
for meth in (
2330+
meth.with_internal_methods(public_methods=public_methods)
2331+
for meth in self.methods.values()
2332+
)
2333+
}
2334+
)

gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,9 @@ class {{ async_method_name_prefix }}{{ service.name }}RestInterceptor:
452452

453453
@property
454454
def {{ name|snake_case }}(self):
455-
return self._{{ name }}(self._session, self._host, self._interceptor) # type: ignore
455+
return self.{{ name|make_private }}(self._session, self._host, self._interceptor) # type: ignore
456456

457-
class _{{ name }}(_Base{{ service.name }}RestTransport._Base{{name}}, {{ async_method_name_prefix }}{{service.name}}RestStub):
457+
class {{ name|make_private }}(_Base{{ service.name }}RestTransport._Base{{name}}, {{ async_method_name_prefix }}{{service.name}}RestStub):
458458
def __hash__(self):
459459
return hash("{{ async_method_name_prefix }}{{ service.name }}RestTransport.{{ name }}")
460460

gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ class {{ service.name }}Transport(abc.ABC):
366366

367367
{% for operations_service in api.get_extended_operations_services(service)|sort(attribute="name") %}
368368
@property
369-
def _{{ operations_service.client_name|snake_case }}(self) -> {{ operations_service.name|snake_case }}.{{ operations_service.client_name }}:
369+
def {{ operations_service.client_name|snake_case|make_private }}(self) -> {{ operations_service.name|snake_case }}.{{ operations_service.client_name }}:
370370
ex_op_service = self._extended_operations_services.get("{{ operations_service.name|snake_case }}")
371371
if not ex_op_service:
372372
ex_op_service = {{ operations_service.name|snake_case }}.{{ operations_service.client_name }}(

gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class {{service.name}}RestTransport(_Base{{ service.name }}RestTransport):
198198

199199
{% endif %}{# service.has_lro #}
200200
{% for method in service.methods.values()|sort(attribute="name") %}
201-
class _{{method.name}}(_Base{{ service.name }}RestTransport._Base{{method.name}}, {{service.name}}RestStub):
201+
class {{ method.name|make_private }}(_Base{{ service.name }}RestTransport._Base{{method.name}}, {{service.name}}RestStub):
202202
def __hash__(self):
203203
return hash("{{service.name}}RestTransport.{{method.name}}")
204204
{% if method.http_options and not method.client_streaming %}

gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ class Async{{service.name}}RestTransport(_Base{{ service.name }}RestTransport):
159159
{{ shared_macros.wrap_async_method_macro()|indent(4) }}
160160

161161
{% for method in service.methods.values()|sort(attribute="name") %}
162-
class _{{method.name}}(_Base{{ service.name }}RestTransport._Base{{method.name}}, Async{{service.name}}RestStub):
162+
class {{ method.name|make_private }}(_Base{{ service.name }}RestTransport._Base{{method.name}}, Async{{service.name}}RestStub):
163163
def __hash__(self):
164164
return hash("Async{{service.name}}RestTransport.{{method.name}}")
165165

gapic/utils/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from gapic.utils.code import empty
2121
from gapic.utils.code import nth
2222
from gapic.utils.code import partition
23+
from gapic.utils.code import make_private
2324
from gapic.utils.doc import doc
2425
from gapic.utils.filename import to_valid_filename
2526
from gapic.utils.filename import to_valid_module_name
@@ -38,6 +39,7 @@
3839
'empty',
3940
'is_msg_field_pb',
4041
'is_str_field_pb',
42+
'make_private',
4143
'nth',
4244
'Options',
4345
'partition',

0 commit comments

Comments
 (0)