Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 31f4559

Browse files
committed
Add a common class for gRPC transport and gRPC AsyncIO transport
1 parent 8cdfec7 commit 31f4559

4 files changed

Lines changed: 190 additions & 164 deletions

File tree

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
@@ -17,7 +17,7 @@ from google.auth import credentials # type: ignore
1717
{% endfor -%}
1818
{% endfilter %}
1919

20-
class {{ service.name }}Transport(metaclass=abc.ABCMeta):
20+
class {{ service.name }}Transport(abc.ABC):
2121
"""Abstract transport class for {{ service.name }}."""
2222

2323
AUTH_SCOPES = (

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

Lines changed: 11 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% extends '_base.py.j2' %}
22

33
{% block content %}
4-
from typing import Callable, Dict, Tuple
4+
from typing import Callable, Dict, Sequence, Tuple
55

66
from google.api_core import grpc_helpers # type: ignore
77
{%- if service.has_lro %}
@@ -19,10 +19,10 @@ import grpc # type: ignore
1919
{{ method.output.ident.python_import }}
2020
{% endfor -%}
2121
{% endfilter %}
22-
from .base import {{ service.name }}Transport
22+
from .grpc_base import {{ service.name }}GrpcBaseTransport
2323

2424

25-
class {{ service.name }}GrpcTransport({{ service.name }}Transport):
25+
class {{ service.name }}GrpcTransport({{ service.name }}GrpcBaseTransport[grpc.Channel]):
2626
"""gRPC backend transport for {{ service.name }}.
2727

2828
{{ service.meta.doc|rst(width=72, indent=4) }}
@@ -34,75 +34,12 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
3434
It sends protocol buffers over the wire using gRPC (which is built on
3535
top of HTTP/2); the ``grpcio`` package must be installed.
3636
"""
37-
def __init__(self, *,
38-
host: str{% if service.host %} = '{{ service.host }}'{% endif %},
39-
credentials: credentials.Credentials = None,
40-
channel: grpc.Channel = None,
41-
api_mtls_endpoint: str = None,
42-
client_cert_source: Callable[[], Tuple[bytes, bytes]] = None) -> None:
43-
"""Instantiate the transport.
44-
45-
Args:
46-
host ({% if service.host %}Optional[str]{% else %}str{% endif %}):
47-
{{- ' ' }}The hostname to connect to.
48-
credentials (Optional[google.auth.credentials.Credentials]): The
49-
authorization credentials to attach to requests. These
50-
credentials identify the application to the service; if none
51-
are specified, the client will attempt to ascertain the
52-
credentials from the environment.
53-
This argument is ignored if ``channel`` is provided.
54-
channel (Optional[grpc.Channel]): A ``Channel`` instance through
55-
which to make calls.
56-
api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If
57-
provided, it overrides the ``host`` argument and tries to create
58-
a mutual TLS channel with client SSL credentials from
59-
``client_cert_source`` or applicatin default SSL credentials.
60-
client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A
61-
callback to provide client SSL certificate bytes and private key
62-
bytes, both in PEM format. It is ignored if ``api_mtls_endpoint``
63-
is None.
64-
65-
Raises:
66-
google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
67-
creation failed for any reason.
68-
"""
69-
if channel:
70-
# Sanity check: Ensure that channel and credentials are not both
71-
# provided.
72-
credentials = False
73-
74-
# If a channel was explicitly provided, set it.
75-
self._grpc_channel = channel
76-
elif api_mtls_endpoint:
77-
host = api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443"
78-
79-
# Create SSL credentials with client_cert_source or application
80-
# default SSL credentials.
81-
if client_cert_source:
82-
cert, key = client_cert_source()
83-
ssl_credentials = grpc.ssl_channel_credentials(
84-
certificate_chain=cert, private_key=key
85-
)
86-
else:
87-
ssl_credentials = SslCredentials().ssl_credentials
88-
89-
# create a new channel. The provided one is ignored.
90-
self._grpc_channel = grpc_helpers.create_channel(
91-
host,
92-
credentials=credentials,
93-
ssl_credentials=ssl_credentials,
94-
scopes=self.AUTH_SCOPES,
95-
)
96-
97-
# Run the base constructor.
98-
super().__init__(host=host, credentials=credentials)
99-
self._stubs = {} # type: Dict[str, Callable]
100-
10137

10238
@classmethod
10339
def create_channel(cls,
10440
host: str{% if service.host %} = '{{ service.host }}'{% endif %},
10541
credentials: credentials.Credentials = None,
42+
scopes: Sequence[str] = None,
10643
**kwargs) -> grpc.Channel:
10744
"""Create and return a gRPC channel object.
10845
Args:
@@ -112,35 +49,23 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
11249
credentials identify this application to the service. If
11350
none are specified, the client will attempt to ascertain
11451
the credentials from the environment.
52+
scopes (Sequence[str]): A optional list of scopes needed for this
53+
service. These are only used when credentials are not specified and
54+
are passed to :func:`google.auth.default`.
11555
kwargs (Optional[dict]): Keyword arguments, which are passed to the
11656
channel creation.
11757
Returns:
11858
grpc.Channel: A gRPC channel object.
11959
"""
60+
if scopes is None:
61+
scopes = cls.AUTH_SCOPES
12062
return grpc_helpers.create_channel(
12163
host,
12264
credentials=credentials,
123-
scopes=cls.AUTH_SCOPES,
65+
scopes=scopes,
12466
**kwargs
12567
)
12668

127-
@property
128-
def grpc_channel(self) -> grpc.Channel:
129-
"""Create the channel designed to connect to this service.
130-
131-
This property caches on the instance; repeated calls return
132-
the same channel.
133-
"""
134-
# Sanity check: Only create a new channel if we do not already
135-
# have one.
136-
if not hasattr(self, '_grpc_channel'):
137-
self._grpc_channel = self.create_channel(
138-
self._host,
139-
credentials=self._credentials,
140-
)
141-
142-
# Return the channel from cache.
143-
return self._grpc_channel
14469
{%- if service.has_lro %}
14570

14671
@property
@@ -178,17 +103,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
178103
A function that, when called, will call the underlying RPC
179104
on the server.
180105
"""
181-
# Generate a "stub function" on-the-fly which will actually make
182-
# the request.
183-
# gRPC handles serialization and deserialization, so we just need
184-
# to pass in the functions for each.
185-
if '{{ method.name|snake_case }}' not in self._stubs:
186-
self._stubs['{{ method.name|snake_case }}'] = self.grpc_channel.{{ method.grpc_stub_type }}(
187-
'/{{ '.'.join(method.meta.address.package) }}.{{ service.name }}/{{ method.name }}',
188-
request_serializer={{ method.input.ident }}.{% if method.input.ident.python_import.module.endswith('_pb2') %}SerializeToString{% else %}serialize{% endif %},
189-
response_deserializer={{ method.output.ident }}.{% if method.output.ident.python_import.module.endswith('_pb2') %}FromString{% else %}deserialize{% endif %},
190-
)
191-
return self._stubs['{{ method.name|snake_case }}']
106+
return super().{{ method.name|snake_case }}
192107
{%- endfor %}
193108

194109

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

Lines changed: 12 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
{% extends '_base.py.j2' %}
22

33
{% block content %}
4-
from typing import Awaitable, Callable, Dict
4+
from typing import Awaitable, Callable, Dict, Sequence, Tuple
55

66
from google.api_core import grpc_helpers_async # type: ignore
77
{%- if service.has_lro %}
88
from google.api_core import operations_v1 # type: ignore
99
{%- endif %}
1010
from google.auth import credentials # type: ignore
1111

12-
import grpc # type: ignore
1312
from grpc.experimental import aio # type: ignore
1413

1514
{% filter sort_lines -%}
@@ -18,10 +17,10 @@ from grpc.experimental import aio # type: ignore
1817
{{ method.output.ident.python_import }}
1918
{% endfor -%}
2019
{% endfilter %}
21-
from .base import {{ service.name }}Transport
20+
from .grpc_base import {{ service.name }}GrpcBaseTransport
2221

2322

24-
class {{ service.name }}GrpcAsyncIOTransport({{ service.name }}Transport):
23+
class {{ service.grpc_asyncio_transport_name }}({{ service.name }}GrpcBaseTransport[aio.Channel]):
2524
"""gRPC AsyncIO backend transport for {{ service.name }}.
2625

2726
{{ service.meta.doc|rst(width=72, indent=4) }}
@@ -33,44 +32,12 @@ class {{ service.name }}GrpcAsyncIOTransport({{ service.name }}Transport):
3332
It sends protocol buffers over the wire using gRPC (which is built on
3433
top of HTTP/2); the ``grpcio`` package must be installed.
3534
"""
36-
_stubs: Dict[str, Callable]
37-
_grpc_channel: aio.Channel
38-
39-
def __init__(self, *,
40-
host: str{% if service.host %} = '{{ service.host }}'{% endif %},
41-
credentials: credentials.Credentials = None,
42-
channel: aio.Channel = None) -> None:
43-
"""Instantiate the transport.
44-
45-
Args:
46-
host ({% if service.host %}Optional[str]{% else %}str{% endif %}):
47-
{{- ' ' }}The hostname to connect to.
48-
credentials (Optional[google.auth.credentials.Credentials]): The
49-
authorization credentials to attach to requests. These
50-
credentials identify the application to the service; if none
51-
are specified, the client will attempt to ascertain the
52-
credentials from the environment.
53-
This argument is ignored if ``channel`` is provided.
54-
channel (Optional[aio.Channel]): A ``Channel`` instance through
55-
which to make calls.
56-
"""
57-
# Sanity check: Ensure that channel and credentials are not both
58-
# provided.
59-
if channel:
60-
credentials = False
61-
62-
# Run the base constructor.
63-
super().__init__(host=host, credentials=credentials)
64-
self._stubs = {} # type: Dict[str, Callable]
65-
66-
# If a channel was explicitly provided, set it.
67-
if channel:
68-
self._grpc_channel = channel
6935

7036
@classmethod
7137
def create_channel(cls,
7238
host: str{% if service.host %} = '{{ service.host }}'{% endif %},
7339
credentials: credentials.Credentials = None,
40+
scopes: Sequence[str] = None,
7441
**kwargs) -> aio.Channel:
7542
"""Create and return a gRPC AsyncIO channel object.
7643
Args:
@@ -80,35 +47,23 @@ class {{ service.name }}GrpcAsyncIOTransport({{ service.name }}Transport):
8047
credentials identify this application to the service. If
8148
none are specified, the client will attempt to ascertain
8249
the credentials from the environment.
50+
scopes (Sequence[str]): A optional list of scopes needed for this
51+
service. These are only used when credentials are not specified and
52+
are passed to :func:`google.auth.default`.
8353
kwargs (Optional[dict]): Keyword arguments, which are passed to the
8454
channel creation.
8555
Returns:
8656
aio.Channel: A gRPC AsyncIO channel object.
8757
"""
58+
if scopes is None:
59+
scopes = cls.AUTH_SCOPES
8860
return grpc_helpers_async.create_channel(
8961
host,
9062
credentials=credentials,
91-
scopes=cls.AUTH_SCOPES,
63+
scopes=scopes,
9264
**kwargs
9365
)
9466

95-
@property
96-
def grpc_channel(self) -> aio.Channel:
97-
"""Create the channel designed to connect to this service.
98-
99-
This property caches on the instance; repeated calls return
100-
the same channel.
101-
"""
102-
# Sanity check: Only create a new channel if we do not already
103-
# have one.
104-
if not hasattr(self, '_grpc_channel'):
105-
self._grpc_channel = self.create_channel(
106-
self._host,
107-
credentials=self._credentials,
108-
)
109-
110-
# Return the channel from cache.
111-
return self._grpc_channel
11267
{%- if service.has_lro %}
11368

11469
@property
@@ -142,21 +97,11 @@ class {{ service.name }}GrpcAsyncIOTransport({{ service.name }}Transport):
14297

14398
Returns:
14499
Callable[[~.{{ method.input.name }}],
145-
~.{{ method.output.name }}]:
100+
Awaitable[~.{{ method.output.name }}]]:
146101
A function that, when called, will call the underlying RPC
147102
on the server.
148103
"""
149-
# Generate a "stub function" on-the-fly which will actually make
150-
# the request.
151-
# gRPC handles serialization and deserialization, so we just need
152-
# to pass in the functions for each.
153-
if '{{ method.name|snake_case }}' not in self._stubs:
154-
self._stubs['{{ method.name|snake_case }}'] = self.grpc_channel.{{ method.grpc_stub_type }}(
155-
'/{{ '.'.join(method.meta.address.package) }}.{{ service.name }}/{{ method.name }}',
156-
request_serializer={{ method.input.ident }}.{% if method.input.ident.python_import.module.endswith('_pb2') %}SerializeToString{% else %}serialize{% endif %},
157-
response_deserializer={{ method.output.ident }}.{% if method.output.ident.python_import.module.endswith('_pb2') %}FromString{% else %}deserialize{% endif %},
158-
)
159-
return self._stubs['{{ method.name|snake_case }}']
104+
return super().{{ method.name|snake_case }}
160105
{%- endfor %}
161106

162107

0 commit comments

Comments
 (0)